Scheduler: rectify λ-post API

...to bring it more in line with all the other calls dealing with Activity*
...allows also to harmonise the ActivityLang::dispatchChain()
...and to compose the calls in Scheduler directly

NOTE: there is a twist: our string-formatting helper did not render
custom string conversions for objects passed as pointer. This was a
long standing problem, caused by ambiguous templates overloads;
now I've attempted to solve it one level more down, in util::StringConv.
This solution may turn out brittle, since we need to exclude any direct
string conversion, most notably the ones for C-Strings (const char*)

In case this solution turns out unsustainable, please feel free
to revert this API change, and return to passing Activity& in λ-post,
because in the end this is cosmetics.
This commit is contained in:
Fischlurch 2023-10-23 01:48:46 +02:00
parent a21057bdf2
commit b61ca94ee5
14 changed files with 142 additions and 56 deletions

View file

@ -172,8 +172,11 @@ namespace util {
* cv-qualifiactions and references are stripped from the type
* @note Deliberately there is no magic detection/support for pointers.
* This function _must not be overloaded_ (to avoid ambiguities
* in more elaborate template instantiations). If you want pointers
* to be indicated (with address), please use util::showPtr explicitly.
* in more elaborate template instantiations).
* @remark Since 10/2023 an attempt was made to solve this problem down
* in `StringConf<TY>`, yet this solution might be brittle. If you
* want pointers to be indicated (with address), consider using
* util::showPtr explicitly.
*/
template<typename TY>
inline std::string

View file

@ -385,16 +385,47 @@ namespace util {
catch(...) { return FAILURE_INDICATOR; }
};
/** specialisation to allow rendering pointers to string-convertible types.
* @note different than #showPtr(), the address is not rendered here.
* @warning could be brittle, due to overlap with lib::meta::enable_CustomStringConversion;
* whenever there is an explicit string conversion, it must be excluded here.
*/
template<typename X>
struct StringConv<X*, lib::meta::disable_if<std::__or_<std::is_same<std::remove_cv_t<X>, char>
,std::is_same<std::remove_cv_t<X>, void>
> >>
{
static std::string
invoke (X const* ptr) noexcept
try { return ptr? "↗" + StringConv<X>::invoke(*ptr)
: BOTTOM_INDICATOR + "«"+typeStr(ptr)+"»";
}
catch(...) { return FAILURE_INDICATOR; }
};
// NOTE: this is meant to be extensible;
// more specialisations are e.g. in format-obj.hpp
/** human readable display of boolean values
* @return "`true`" or "`false`"
*/
inline std::string
showBool (bool yes) noexcept
{
return yes? lib::meta::BOOL_TRUE_STR
: lib::meta::BOOL_FALSE_STR;
}
/** pretty-print a double in fixed-point format */
std::string showDouble (double) noexcept;
std::string showFloat (float) noexcept;
/** pretty-print an address as hex-suffix */
std::string showAddr (void const* addr) noexcept;
@ -425,17 +456,6 @@ namespace util {
: BOTTOM_INDICATOR + " «" + typeStr(smPtr) + "»";
}
/** human readable display of boolean values
* @return "`true`" or "`false`"
*/
inline std::string
showBool (bool yes) noexcept
{
return yes? lib::meta::BOOL_TRUE_STR
: lib::meta::BOOL_FALSE_STR;
}
}// namespace util
#endif /*LIB_META_UTIL_H*/

View file

@ -121,11 +121,12 @@ namespace gear {
*/
template<class EXE>
static activity::Proc
dispatchChain (Activity& chain, EXE& executionCtx)
dispatchChain (Activity* chain, EXE& executionCtx)
{
activity::Proc res = chain.dispatch (executionCtx.getSchedTime(), executionCtx);
if (!chain) return activity::PASS;
activity::Proc res = chain->dispatch (executionCtx.getSchedTime(), executionCtx);
if (activity::PASS == res)
res = activateChain (chain.next, executionCtx);
res = activateChain (chain->next, executionCtx);
else if (activity::SKIP == res)
res = activity::PASS;
return res;

View file

@ -182,7 +182,7 @@ namespace gear {
constexpr void
_verify_usable_as_ExecutionContext ()
{
ASSERT_MEMBER_FUNCTOR (&EXE::post, Proc(Time, Activity&, EXE&));
ASSERT_MEMBER_FUNCTOR (&EXE::post, Proc(Time, Activity*, EXE&));
ASSERT_MEMBER_FUNCTOR (&EXE::work, void(Time, size_t));
ASSERT_MEMBER_FUNCTOR (&EXE::done, void(Time, size_t));
ASSERT_MEMBER_FUNCTOR (&EXE::tick, Proc(Time));
@ -485,7 +485,7 @@ namespace gear {
activity::Proc
dispatchNotify (Time now, EXE& executionCtx)
{
return executionCtx.post (now, *this, executionCtx);
return executionCtx.post (now, this, executionCtx);
}
template<class EXE>
@ -531,7 +531,7 @@ namespace gear {
activity::Proc
dispatchSelf (Time when, EXE& executionCtx)
{
return executionCtx.post (when, *this, executionCtx);
return executionCtx.post (when, this, executionCtx);
}
template<class EXE>
@ -547,7 +547,7 @@ namespace gear {
postChain (Time when, EXE& executionCtx)
{
REQUIRE (next);
return executionCtx.post (when, *next, executionCtx);
return executionCtx.post (when, next, executionCtx);
}
template<class EXE>

View file

@ -216,7 +216,7 @@ namespace gear {
Time now = executionCtx.getSchedTime();
if (decideDispatchNow (when, now))
return ActivityLang::dispatchChain (*chain, executionCtx);
return ActivityLang::dispatchChain (chain, executionCtx);
else
if (holdsGroomingToken (thisThread()))
layer1.feedPrioritisation (*chain, when);

View file

@ -246,9 +246,9 @@ namespace gear {
* @remark the \a ctx argument is redundant (helpful for test/mocking)
*/
activity::Proc
post (Time when, Activity& chain, ExecutionCtx& ctx)
post (Time when, Activity* chain, ExecutionCtx& ctx)
{
return layer2_.postDispatch (&chain, when, ctx, layer1_);
return layer2_.postDispatch (chain, when, ctx, layer1_);
}
void
@ -307,8 +307,8 @@ namespace gear {
loadControl_.incomingCapacity (head,now));
})
.performStep([&]{
Activity* act = layer2_.findWork(layer1_,now);
return layer2_.postDispatch (act, now, ctx, layer1_);
Activity* act = layer2_.findWork (layer1_,now);
return ctx.post (now, act, ctx);
})
.performStep([&]{ return scatteredDelay(
loadControl_.outgoingCapacity (head,now));

View file

@ -32,7 +32,7 @@ END
TEST "Scheduler Load-Control" SchedulerLoadControl_test <<END
PLANNED "Scheduler Load-Control" SchedulerLoadControl_test <<END
return: 0
END

View file

@ -115,20 +115,20 @@ namespace test {
Reticent closeLipped;
UnReticent chatterer;
CHECK (toString (closeLipped) == "«Reticent»" );
CHECK (toString (chatterer) == "hey Joe!" );
CHECK (toString (closeLipped) == "«Reticent»"_expect);
CHECK (toString (chatterer) == "hey Joe!"_expect);
CHECK (toString (&chatterer) == "«UnReticent»"); // string convertible => type display
CHECK (toString (nullptr) == "↯" ); // runtime exception, caught
CHECK (toString (&chatterer) == "↗hey Joe!"_expect); // pointer indicated
CHECK (toString (nullptr) == "↯"_expect); // runtime exception, caught
CHECK (toString (true) == "true" ); // special handling for bool
CHECK (toString (2+2 == 5) == "false" );
CHECK (toString (12.34e55) == "1.234e+56" );
CHECK (toString (true) == "true"_expect); // special handling for bool
CHECK (toString (2+2 == 5) == "false"_expect);
CHECK (toString (12.34e55) == "1.234e+56"_expect);
CHECK (toString (short(12))
+toString (345L)
+toString ("67")
+toString ('8') == "12345678" ); // these go through lexical_cast<string>
+toString ('8') == "12345678"_expect); // these go through lexical_cast<string>
}
@ -151,7 +151,7 @@ namespace test {
for (auto s : ss)
res += s;
CHECK (res == "..2.113.114.115.116.117.118.119.1110.11");
CHECK (res == "..2.113.114.115.116.117.118.119.1110.11"_expect);
using VecS = vector<string>;

View file

@ -173,16 +173,16 @@ namespace test {
CHECK (dump(lateBindInstance (dummy, tuple<>{})) == "()"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{42})) == "(42)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{1,2,3})) == "(1,2,3)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{marker,2,3})) == "(«long»,2,3)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{1,marker,3})) == "(1,«long»,3)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{1,2,marker})) == "(1,2,«long»)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{marker})) == "(«long»)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{marker,2,3})) == "(↗555,2,3)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{1,marker,3})) == "(1,↗555,3)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{1,2,marker})) == "(1,2,↗555)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{marker})) == "(↗555)"_expect);
CHECK (dump(lateBindInstance (dummy, tuple{string{"1"}
,"2"
,marker
,tuple(2,3)
, 5.5
})) == "(1,2,«long»,«tuple<int, int>»──(2,3),5.5)"_expect);
})) == "(1,2,↗555,«tuple<int, int>»──(2,3),5.5)"_expect);
}
};

View file

@ -232,8 +232,8 @@ namespace test {
ctx.done (t,x);
CHECK (detector.verifyInvocation(CTX_DONE).arg(t,x));
CHECK (activity::PASS == ctx.post (t, a, ctx));
CHECK (detector.verifyInvocation(CTX_POST).arg(t,a,ctx));
CHECK (activity::PASS == ctx.post (t, &a, ctx));
CHECK (detector.verifyInvocation(CTX_POST).arg(t,&a,ctx));
CHECK (activity::PASS == ctx.tick(t));
CHECK (detector.verifyInvocation(CTX_TICK).arg(t));

View file

@ -544,7 +544,7 @@ namespace test {
struct FakeExecutionCtx;
using SIG_post = activity::Proc(Time, Activity&, FakeExecutionCtx&);
using SIG_post = activity::Proc(Time, Activity*, FakeExecutionCtx&);
using SIG_work = void(Time, size_t);
using SIG_done = void(Time, size_t);
using SIG_tick = activity::Proc(Time);

View file

@ -418,14 +418,14 @@ namespace test {
// insert instrumentation to trace activation
detector.watchGate (post.next, "Gate");
CHECK (activity::PASS == ActivityLang::dispatchChain (post, detector.executionCtx)); // start execution (case/seq == 0)
CHECK (activity::PASS == ActivityLang::dispatchChain (&post, detector.executionCtx)); // start execution (case/seq == 0)
CHECK (detector.verifyInvocation("Gate") .arg("1.011 ⧐ Act(GATE") // ...first the Gate was activated
.beforeInvocation("after-Gate").arg("1.011 ⧐ Act(TICK") // ...then activation passed out of Gate...
.beforeInvocation("CTX-tick") .arg("1.011")); // ...and finally the TICK invoked the λ-tick
detector.incrementSeq();
gate.data_.condition.incDependencies(); // Gate is blocked
CHECK (activity::PASS == ActivityLang::dispatchChain (post, detector.executionCtx)); // start execution (case/seq == 1)
CHECK (activity::PASS == ActivityLang::dispatchChain (&post, detector.executionCtx)); // start execution (case/seq == 1)
CHECK (detector.verifyInvocation("Gate") .seq(1).arg("1.011 ⧐ Act(GATE") // ...the Gate was activated...
.beforeInvocation("CTX-post").seq(1).arg("2.011","Act(GATE","≺test::CTX≻")); // ...but was found blocked and re-scheduled itself to 2.011
CHECK (detector.ensureNoInvocation("after-Gate").seq(1) // verify activation was not passed out behind Gate
@ -436,7 +436,7 @@ namespace test {
detector.incrementSeq();
Activity notify{post.next}; // Notification via instrumented connection to the Gate
CHECK (activity::PASS == ActivityLang::dispatchChain (notify, detector.executionCtx)); // dispatch a notification (case/seq == 2)
CHECK (activity::PASS == ActivityLang::dispatchChain (&notify, detector.executionCtx)); // dispatch a notification (case/seq == 2)
CHECK (detector.verifyInvocation("Gate") .seq(2).arg("1.011 --notify-↯> Act(GATE") // ...notification dispatched towards the Gate
.beforeInvocation("CTX-post").seq(2).arg("1.011","after-Gate","≺test::CTX≻")); // ...this opened the Gate and posted/requested activation of the rest of the chain
CHECK (detector.ensureNoInvocation("after-Gate").seq(2) // verify that activation was not passed out directly
@ -478,7 +478,7 @@ namespace test {
// insert instrumentation to trace activation
detector.watchGate (anchor.next, "theGate");
CHECK (activity::PASS == ActivityLang::dispatchChain (anchor, detector.executionCtx));
CHECK (activity::PASS == ActivityLang::dispatchChain (&anchor, detector.executionCtx));
CHECK (detector.verifyInvocation("theGate").arg("5.105 ⧐ Act(GATE")
.beforeInvocation("after-theGate").arg("⧐ Act(WORKSTART")
@ -535,7 +535,7 @@ namespace test {
// rig the λ-post to forward dispatch as expected in real usage
detector.executionCtx.post.implementedAs(
[&](Time when, Activity& postedAct, auto& ctx)
[&](Time when, Activity* postedAct, auto& ctx)
{
if (when == ctx.getSchedTime()) // only for POST to run „right now“
return ActivityLang::dispatchChain (postedAct, ctx);
@ -544,7 +544,7 @@ namespace test {
});
///// Case-1 : send a notification from prerequisite, but prior to activating primary-chain
CHECK (activity::PASS == ActivityLang::dispatchChain (trigger, detector.executionCtx));
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
.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
@ -552,7 +552,7 @@ namespace test {
///// Case-2 : now activate the primary-chain
detector.incrementSeq();
CHECK (activity::PASS == ActivityLang::dispatchChain (anchor, detector.executionCtx));
CHECK (activity::PASS == ActivityLang::dispatchChain (&anchor, detector.executionCtx));
CHECK (detector.verifyInvocation("CTX-post").seq(1).arg("5.555","Act(NOTIFY","≺test::CTX≻") // immediately at begin, the internal self-notification is posted
.beforeInvocation("deBlock") .seq(1).arg("5.555 --notify-↯> Act(GATE") // ...and then dispatched recursively via ActivityLang::dispatchChain() towards the Gate
.arg("<1, until 0:00:10.000") // Note: at this point, the Gate-latch expects 1 notifications
@ -567,7 +567,7 @@ namespace test {
.afterInvocation("CTX-done").seq(1));
detector.incrementSeq();
CHECK (activity::PASS == ActivityLang::dispatchChain (trigger, detector.executionCtx)); // and any further external trigger is likewise blocked:
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.ensureNoInvocation("testJob") // ==> no further invocation
@ -620,7 +620,7 @@ namespace test {
///// Case-1 : trigger off the async IO job
CHECK (activity::PASS == ActivityLang::dispatchChain (anchor, detector.executionCtx));
CHECK (activity::PASS == ActivityLang::dispatchChain (&anchor, detector.executionCtx));
CHECK (detector.verifyInvocation("CTX-work").seq(0).arg("5.555", "") // activation of WORKSTART
.beforeInvocation("loadJob") .seq(0).arg("7.007", 12345)); // activation of JobFunctor
CHECK (detector.ensureNoInvocation("CTX-done").seq(0) // IO operation just runs, no further activity yet
@ -629,7 +629,7 @@ namespace test {
///// Case-2 : activate the rest of the chain after IO is done
detector.incrementSeq();
CHECK (activity::PASS == ActivityLang::dispatchChain (notify, detector.executionCtx));
CHECK (activity::PASS == ActivityLang::dispatchChain (&notify, 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", "Act(NOTIFY", "≺test::CTX≻")); // the following NOTIFY is posted...
}
@ -669,7 +669,7 @@ namespace test {
// insert test-instrumentation
detector.insertActivationTap(anchor.next);
CHECK (activity::PASS == ActivityLang::dispatchChain (anchor, detector.executionCtx));
CHECK (activity::PASS == ActivityLang::dispatchChain (&anchor, detector.executionCtx));
CHECK (detector.verifyInvocation("tap-INVOKE").arg("5.555 ⧐ Act(INVOKE")
.beforeInvocation("metaJob") .arg("7.007",12345));

View file

@ -396,7 +396,7 @@ namespace test {
CHECK (fullCnt == wof.size());
wof.activate (2.0);
sleep_for(5ms);
sleep_for(10ms);
CHECK (2*fullCnt == uniqueCnt);
CHECK (2*fullCnt == wof.size());

View file

@ -81728,8 +81728,9 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1692380071745" ID="ID_708893412" MODIFIED="1692404214990" TEXT="offensichtlicher Grund: es ist ein Pointer">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#5b280f" CREATED="1692404216754" FOLDED="true" ID="ID_1806024569" MODIFIED="1692404238247" TEXT="util::toString sollte sich bei Pointern sinnvoll verhalten">
<node COLOR="#435e98" CREATED="1692404216754" FOLDED="true" ID="ID_1806024569" MODIFIED="1698018109478" TEXT="util::toString sollte sich bei Pointern sinnvoll verhalten">
<icon BUILTIN="button_cancel"/>
<icon BUILTIN="button_ok"/>
<node COLOR="#5b280f" CREATED="1692404252108" ID="ID_842776495" MODIFIED="1692404264513" TEXT="versucht und gescheitert">
<icon BUILTIN="stop-sign"/>
</node>
@ -81751,6 +81752,9 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1692404432332" ID="ID_1781762482" MODIFIED="1692404442926" TEXT="toString soll zuverl&#xe4;ssig und minimal sein"/>
<node CREATED="1692404446027" ID="ID_108331302" MODIFIED="1692404462356" TEXT="Spezialbehandlung f&#xfc;r Pointer ist nicht immer erw&#xfc;nscht"/>
</node>
<node COLOR="#435e98" CREATED="1698018026613" ID="ID_1423551682" LINK="#ID_1691641418" MODIFIED="1698018107180" TEXT="10/2023 : hab jetzt doch eine L&#xf6;sung gefunden">
<icon BUILTIN="idea"/>
</node>
</node>
<node COLOR="#338800" CREATED="1692404471311" ID="ID_437668247" MODIFIED="1692404494054" TEXT="in diesem Fall einfach utill::showPtr() verwenden">
<icon BUILTIN="button_ok"/>
@ -82142,6 +82146,64 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<arrowlink COLOR="#87a6b3" DESTINATION="ID_1875907326" ENDARROW="Default" ENDINCLINATION="543;939;" ID="Arrow_ID_1742040512" STARTARROW="None" STARTINCLINATION="271;21;"/>
</node>
</node>
<node COLOR="#338800" CREATED="1698017543187" FOLDED="true" ID="ID_947237772" MODIFIED="1698018075769" TEXT="aber man kann das API harmonisieren und einen Activity* durchgeben">
<icon BUILTIN="button_ok"/>
<node CREATED="1698017576870" ID="ID_130404848" MODIFIED="1698017587979" TEXT="dadurch wird insgesamt alles symmetrischer"/>
<node CREATED="1698017588527" ID="ID_107905550" MODIFIED="1698017627358" TEXT="auch ActivityLang::dispatchChain vs ActivityLang:.activateChain"/>
<node CREATED="1698017628162" ID="ID_985088218" MODIFIED="1698017660790">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
und ich habe im Scheduler nur noch &#955;-post als<i>&#160;universellen Eingang</i>
</p>
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#c8c0b6" CREATED="1698017665997" ID="ID_1014553523" MODIFIED="1698017924884" TEXT="(leider eine Schei&#xdf; Arbeit)">
<icon BUILTIN="smiley-oh"/>
<node CREATED="1698017681299" ID="ID_1656316113" MODIFIED="1698017696205" TEXT="vor allem wegen util::stringify()"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1698017696767" ID="ID_1387271874" MODIFIED="1698017710995" TEXT="das bekannte Problem mit dem Rendern von Pointern">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1698017811570" ID="ID_529684124" MODIFIED="1698017823691" TEXT="util::toString kann nicht spezialisiert werden"/>
<node CREATED="1698017824760" ID="ID_181113602" MODIFIED="1698017847198" TEXT="und im Level darunter k&#xe4;mpft man mit ambivalenten Spezialisierungen"/>
</node>
<node CREATED="1698017714703" ID="ID_1691641418" MODIFIED="1698017789160" TEXT="L&#xf6;sung gefunden: StringConv&lt;T*&gt;">
<icon BUILTIN="idea"/>
<node CREATED="1698017730180" ID="ID_822300883" MODIFIED="1698017740207" TEXT="aber mit explizitem Ausschlu&#xdf; von C-Strings"/>
<node CREATED="1698017740884" ID="ID_202028587" MODIFIED="1698017749548" TEXT="...und von void*"/>
<node CREATED="1698017750482" ID="ID_1459125272" MODIFIED="1698017756861" TEXT="...und von const void*"/>
<node CREATED="1698017757730" ID="ID_435030620" MODIFIED="1698017763326" TEXT="oh my">
<icon BUILTIN="smiley-angry"/>
</node>
<node COLOR="#b40a9b" CREATED="1698017766341" ID="ID_546066467" MODIFIED="1698017781763" TEXT="daswennmangutgeht...">
<font NAME="SansSerif" SIZE="10"/>
</node>
</node>
<node COLOR="#338800" CREATED="1698017850948" ID="ID_1029506488" MODIFIED="1698017920437" TEXT="lasse das Rendern der Addresse hier bewu&#xdf;t weg">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<ul>
<li>
weil sie die Ausgabe aufbl&#228;ht
</li>
<li>
weil sie i.d.R. zuf&#228;llig ist
</li>
<li>
es gibt ja noch util::showPtr()
</li>
<li>
und in format-cout hab ich's drinnen gelassen (da m&#246;chte man es normalerweise schon sehen)
</li>
</ul>
</body>
</html></richcontent>
<icon BUILTIN="yes"/>
</node>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1697763699242" ID="ID_272943268" MODIFIED="1697763810547" TEXT="EngineObserver-Schnittstelle">