Scheduler-test: a helper for one-time operations

Invent a special JobFunctor...
 - can be created / bound from a λ
 - self-manages its storage on the heap
 - can be invoked once, then discards itself

Intention is to pass such one-time actions to the Scheduler
to cause some ad-hoc transitions tied to curren circumstances;
a notable example will be the callback after load-test completion.
This commit is contained in:
Fischlurch 2023-12-08 03:16:57 +01:00
parent 030e9aa8a2
commit 7eca3ffe42
8 changed files with 485 additions and 20 deletions

View file

@ -82,9 +82,15 @@ namespace lib {
* Typically this is followed by activating
* the handle by the managing service.
*/
Handle ( )
: smPtr_()
{ }
Handle() = default;
/** directly establish handle from an implementation,
* which typically way just heap allocated beforehand.
*/
explicit
Handle (IMP* imp)
: smPtr_{imp}
{ }
Handle (Handle const& r) = default;
Handle (Handle && rr) = default;

View file

@ -48,7 +48,7 @@ namespace test{
* i.e. to verify each created instance was properly destroyed after use.
*/
class Dummy
: util::NonCopyable
: util::MoveAssign
{
int val_;
@ -57,6 +57,11 @@ namespace test{
static bool _throw_in_ctor;
public:
virtual ~Dummy() ///< can act as interface
{
checksum() -= val_;
}
Dummy ()
: val_(1 + (rand() % 100000000))
{ init(); }
@ -65,14 +70,24 @@ namespace test{
: val_(v)
{ init(); }
virtual ~Dummy()
Dummy (Dummy && oDummy)
: Dummy(0)
{
checksum() -= val_;
swap (*this, oDummy);
}
virtual long
Dummy&
operator= (Dummy && oDummy)
{
if (&oDummy != this)
swap (*this, oDummy);
return *this;
}
acc (int i) ///< dummy API operation
/** a dummy API operation */
virtual long
acc (int i)
{
return val_+i;
}
@ -93,7 +108,7 @@ namespace test{
friend void
swap (Dummy& dum1, Dummy& dum2) ///< checksum neutral
{
std::swap(dum1.val_, dum2.val_);
std::swap (dum1.val_, dum2.val_);
}
static long&

View file

@ -63,7 +63,7 @@ namespace gear {
JobKind
getJobKind() const
{
return TEST_JOB;
return META_JOB;
}
std::string

View file

@ -0,0 +1,197 @@
/*
SPECIAL-JOB-FUN.hpp - a one-time render job to do something special
Copyright (C) Lumiera.org
2023, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file special-job-fun.hpp
** A configurable one-time job to invoke some _special_ function.
** The actual operation is configured as λ-function and the instance
** manages itself into heap storage and automatically destroys itself
** after the predetermined invocation. The intended usage is to supply
** a specifically wired one-time »fire-and-forget« action to the Scheduler
** as answer so some special processing situation. The front-end handle
** SpecialJobFun itself is disposable and only serves as builder; even
** after the front-end is gone, the actual JobFunctor will maintain
** one self-reference unless it is invoked...
** @warning obviously this is a dangerous tool; the user *must ensure*
** that it is at most *invoked once* after that, the instance
** will self-destroy, leaving a dangling reference.
** @todo WIP 12/2023 invented to help with Scheduler load testing as
** part of the »Playback Vertical Slice« this idea however
** might be generally useful to handle one-time adjustments
** from within a play- or planning process.
** @see SpecialJobFun_test
** @see TestChainLoad::ScheduleCtx::continuation() usage example
*/
#ifndef VAULT_GEAR_SPECIAL_JOB_FUN_H
#define VAULT_GEAR_SPECIAL_JOB_FUN_H
#include "lib/handle.hpp"
#include "vault/gear/nop-job-functor.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/format-string.hpp"
#include "lib/format-obj.hpp"
#include "lib/meta/util.hpp"
#include <string>
#include <memory>
#include <utility>
namespace vault{
namespace gear {
namespace err = lumiera::error;
using util::_Fmt;
using lib::meta::disable_if_self;
using std::move;
using std::forward;
using std::make_shared;
/**
* Interface: JobFunctor configured to invoke a function a limited number of times.
*/
class SpecialFunPrototype
: public NopJobFunctor
{
public:
virtual uint remainingInvocations() const =0;
};
/**
* Front-end to configure a special job functor for one-time use.
*/
class SpecialJobFun
: protected lib::Handle<SpecialFunPrototype>
{
using _Handle = lib::Handle<SpecialFunPrototype>;
template<class FUN>
class SpecialExecutor
: SpecialFunPrototype
{
FUN fun_;
uint lives_{1};
_Handle selfHook_;
public:
explicit
SpecialExecutor (FUN theFun)
: fun_{move (theFun)}
, selfHook_(this)
{ }
/** @internal allow the front-end to attach
* itself to the self-managing hook */
friend _Handle&
selfAttached (SpecialExecutor* instance)
{
REQUIRE (instance->selfHook_);
REQUIRE (instance->remainingInvocations());
// expose the self-managing handle
return instance->selfHook_;
}
uint
remainingInvocations() const
{
return lives_;
}
/* === JobFunctor Interface === */
void
invokeJobOperation (JobParameter param) override
{
if (not remainingInvocations())
throw err::Logic{"invoking deceased SpecialJobFun"
,err::LUMIERA_ERROR_LIFECYCLE};
fun_(param);
if (not --lives_)
selfHook_.close();
} // Suicide.
std::string
diagnostic() const override
{
return _Fmt{"SpecialJob(%d)-%s"}
% lives_
% util::showHash(size_t(this), 2);
}
};
public:
SpecialJobFun() = default;
/**
* Establish a new SpecialJobFun variation
* directly by wrapping a given functor.
* The JobFunctor instance itself will be heap allocated.
* @warning while a direct reference to this JobFunctor can be
* obtained through conversion, this reference must not be
* used after invoking the job one single time; after that,
* this reference is dangling and using it further will
* lead to SEGFAULT or memory corruption.
*/
template<class FUN, typename =disable_if_self<FUN, SpecialJobFun>>
explicit
SpecialJobFun (FUN&& someFun)
: _Handle{selfAttached (new SpecialExecutor(forward<FUN> (someFun)))}
{ }
// standard copy operations acceptable
explicit
operator bool() const
{
return _Handle::isValid()
and 0 < _Handle::impl().remainingInvocations();
}
operator JobClosure&() const
{
REQUIRE (operator bool());
return _Handle::impl();
}
long
use_count() const
{
return _Handle::smPtr_.use_count();
}
};
}} // namespace vault::gear
#endif /*VAULT_GEAR_SPECIAL_JOB_FUN_H*/

View file

@ -44,6 +44,12 @@ END
TEST "Self-managed one-time Job" SpecialJobFun_test <<END
return: 0
END
PLANNED "Synthetic Load for Test" TestChainLoad_test <<END
return: 0
END

View file

@ -333,6 +333,12 @@ namespace test {
return "JobFun-"+string{mockOperation_};
}
JobKind
getJobKind() const
{
return TEST_JOB;
}
public:
MockJobFunctor (MockOp mockedJobOperation)
: mockOperation_{move (mockedJobOperation)}

View file

@ -0,0 +1,191 @@
/*
SpecialJobFun(Test) - verify a disposable configurable job functor
Copyright (C) Lumiera.org
2023, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
/** @file special-job-fun-test.cpp
** unit test \ref SpecialJobFun_test
*/
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
//#include "vault/real-clock.hpp"
//#include "lib/time/timevalue.hpp"
#include "vault/gear/special-job-fun.hpp"
#include "lib/format-cout.hpp" ////////////////////////////////////TODO Moo-oh
#include "lib/test/diagnostic-output.hpp"//////////////////////////TODO TOD-oh
#include "lib/test/testdummy.hpp"
//#include "lib/util.hpp"
//#include <array>
//#include <functional>
//using lib::time::Time;
//using lib::time::FSecs;
//using util::isnil;
//using util::isSameObject;
//using lib::test::randStr;
//using lib::test::randTime;
using lib::test::Dummy;
//using std::array;
namespace vault{
namespace gear {
namespace test {
namespace { // shorthands and parameters for test...
}//(End)test definitions
/*****************************************************************//**
* @test verify a self-managing one-time render job functor.
* @see TestChainLoad_test::usageExample
* @see TestChainLoad::ScheduleCtx::continuation()
* @see special-job-fun.hpp
*/
class SpecialJobFun_test : public Test
{
virtual void
run (Arg)
{
simpleUsage();
verifyLifecycle();
}
/** @test demonstrate simple usage by λ-binding
* @todo WIP 12/23 🔁 define implement
*/
void
simpleUsage()
{
bool hit{false}; // create directly from λ
SpecialJobFun specialFun{[&](JobParameter){ hit=true; }};
CHECK (specialFun);
Job funJob{specialFun
,InvocationInstanceID()
,Time::ANYTIME
};
funJob.triggerJob();
CHECK (hit);
CHECK (not specialFun);
}
/** @test verify storage and lifecycle management
* - use a instance-tracking marker implanted into the functor
* - verify no memory is leaked and the tracker instance is deallocated
* - verify the single tracker instance indeed lives in the JobFunctor
* - investigate the usage count of the front-end handle
* - verify the front-end can be copied without impact on the JobFunctor
* - verify the heap allocated functor keeps itself alive
* even when the front-end handle is already gone.
* - verify the functor de-allocates itself after latst invocation
* @todo WIP 12/23 define implement
*/
void
verifyLifecycle()
{
CHECK (0 == Dummy::checksum());
{ // Note: ▽▽▽ tracker in λ-closure
SpecialJobFun funTrack{[tracker=Dummy(23)]
(JobParameter param) mutable
{
int mark = param.invoKey.part.a;
tracker.setVal (mark);
}}; // △△△ invocation should alter checksum
// one Dummy instance was implanted
CHECK (23 == Dummy::checksum());
InvocationInstanceID hiddenMessage;
hiddenMessage.part.a = 55;
Job funJob{funTrack
,hiddenMessage
,Time::ANYTIME
};
CHECK (23 == Dummy::checksum());
funJob.triggerJob();
CHECK (55 == Dummy::checksum()); // the `funJob` front-end handle still keeps it alive
} // but when this front-end goes out of scope...
CHECK (0 == Dummy::checksum()); // the implanted tracker is also gone
{ // another investigation with the same technique...
auto trackingLambda = [tracker=Dummy(23)]
(JobParameter param) mutable
{
int mark = param.invoKey.part.a;
tracker.setVal (mark);
};
CHECK (23 == Dummy::checksum());
SpecialJobFun frontEnd{move(trackingLambda)}; // this time the λ is moved in..
CHECK (23 == Dummy::checksum()); // the embedded tracker was copied into the Functor in heap memory
CHECK (2 == frontEnd.use_count()); // Note: both the front-end and the Functor in heap hold a use-reference
auto otherHandle = frontEnd; // copy of front-end...
CHECK (3 == frontEnd.use_count()); // ...so there are three usages of the front-end handle now
CHECK (23 == Dummy::checksum()); // ...but only one tracker instance (in heap)
frontEnd = SpecialJobFun(); // re-assign one front-end handle with an empty instance
CHECK (0 == frontEnd.use_count()); // thus `frontEnd` is no longer attached to the active instance
CHECK (2 == otherHandle.use_count()); // but the other copy still is
CHECK (not frontEnd);
CHECK (otherHandle);
InvocationInstanceID hiddenMessage;
hiddenMessage.part.a = 55;
Job funJob{otherHandle // Note: don't pass the handle here, rather a JobFunctor& is extracted
,hiddenMessage
,Time::ANYTIME
};
CHECK (2 == otherHandle.use_count());
CHECK (23 == Dummy::checksum());
otherHandle = SpecialJobFun(); // now kill even the last front-end handle we had
CHECK (0 == otherHandle.use_count()); // thus _we_ have no way to reach the Functor in heap
CHECK (23 == Dummy::checksum()); // yet it stays alive, since it was not invoked yet
funJob.triggerJob(); // after invocation, the Functor in heap memory self-destructs
CHECK (0 == Dummy::checksum()); // since it did hold the last reference
}
CHECK (0 == Dummy::checksum());
}
};
/** Register this test class... */
LAUNCHER (SpecialJobFun_test, "unit engine");
}}} // namespace vault::gear::test

View file

@ -101304,8 +101304,49 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<font NAME="SansSerif" SIZE="8"/>
</node>
</node>
<node CREATED="1701913043245" ID="ID_445438847" MODIFIED="1701913147737" TEXT="bessere Steuerung des Completion-Callback">
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1701913043245" ID="ID_445438847" MODIFIED="1701995114300" TEXT="bessere Steuerung des Completion-Callback">
<linktarget COLOR="#c62f45" DESTINATION="ID_445438847" ENDARROW="Default" ENDINCLINATION="-1207;75;" ID="Arrow_ID_1132943593" SOURCE="ID_1739832819" STARTARROW="None" STARTINCLINATION="-73;-483;"/>
<icon BUILTIN="pencil"/>
<node CREATED="1701995129106" ID="ID_1885461201" MODIFIED="1701995147012">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
soll <i>per Schedule </i>als Letztes laufen
</p>
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1701995148607" ID="ID_1950736877" MODIFIED="1701995170924" TEXT="brauche dazu Hilfsmittel: &#x3bb;-Job">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1702004101716" ID="ID_908134373" MODIFIED="1702004108016" TEXT="SpecialJobFun....">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1702004109424" ID="ID_1293442970" MODIFIED="1702004221556" TEXT="ist eine JobFunctor-Instanz">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1702004121881" ID="ID_1678320281" MODIFIED="1702004221556" TEXT="bettet das gegebene &#x3bb; ein">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1702004142348" ID="ID_702114798" MODIFIED="1702004221556" TEXT="h&#xe4;lt sich selber zirkul&#xe4;r per smart-ptr am Leben">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1702004163960" ID="ID_289314383" MODIFIED="1702004221557" TEXT="s&#xe4;gt sich selber ab &#x2014; nach erfolgtem Aufruf">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1702004207926" ID="ID_82682167" MODIFIED="1702004221557" TEXT="front-End-Handle : als Builder und Accessor">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1702004224915" ID="ID_1713843451" MODIFIED="1702004237147" TEXT="SpecialJobFun_test">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1702004230770" ID="ID_1088072058" MODIFIED="1702004234360" TEXT="simpleUsage">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1702008468565" ID="ID_1701934123" MODIFIED="1702008471652" TEXT="verifyLifecycle">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1701814806243" ID="ID_1359675923" MODIFIED="1701823453699" TEXT="Job-Funktoren">
@ -101389,15 +101430,16 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<arrowlink COLOR="#bb1868" DESTINATION="ID_120274201" ENDARROW="Default" ENDINCLINATION="-730;116;" ID="Arrow_ID_573558112" STARTARROW="None" STARTINCLINATION="-428;0;"/>
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1701838598906" ID="ID_1235285916" MODIFIED="1701838619227" TEXT="bleibt h&#xe4;ngen &#x27f6; Timeout">
<node COLOR="#435e98" CREATED="1701838598906" ID="ID_1235285916" MODIFIED="1701990865812" TEXT="bleibt h&#xe4;ngen &#x27f6; Timeout">
<icon BUILTIN="broken-line"/>
<node CREATED="1701843677557" ID="ID_619867364" MODIFIED="1701879688239" TEXT="offensichtlicher Fehler: mu&#xdf; dem ersten Plan-Job nat&#xfc;rlich einen Offset geben">
<node COLOR="#338800" CREATED="1701843677557" ID="ID_619867364" MODIFIED="1701990950803" TEXT="offensichtlicher Fehler: mu&#xdf; dem ersten Plan-Job nat&#xfc;rlich einen Offset geben">
<arrowlink COLOR="#3c5b80" DESTINATION="ID_1631524816" ENDARROW="Default" ENDINCLINATION="84;-7;" ID="Arrow_ID_1725054435" STARTARROW="None" STARTINCLINATION="80;3;"/>
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1701900943218" HGAP="39" ID="ID_1117609340" MODIFIED="1701900956080" TEXT="planningJob(calcNextLevel(0)-1)" VSHIFT="-1">
<icon BUILTIN="idea"/>
</node>
</node>
<node COLOR="#435e98" CREATED="1701843700258" ID="ID_1562297081" MODIFIED="1701976295979" TEXT="aber dennoch sollte dann der Lauf irgendwann starten (dann halt 64ms sp&#xe4;ter)">
<node COLOR="#435e98" CREATED="1701843700258" FOLDED="true" ID="ID_1562297081" MODIFIED="1701976295979" TEXT="aber dennoch sollte dann der Lauf irgendwann starten (dann halt 64ms sp&#xe4;ter)">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1701906561917" ID="ID_759675868" MODIFIED="1701906661639" TEXT="Situation: nur wenige Jobs zu Beginn &#x2014; und die wedern sofort dispatched">
<linktarget COLOR="#5c468c" DESTINATION="ID_759675868" ENDARROW="Default" ENDINCLINATION="-72;-107;" ID="Arrow_ID_845853929" SOURCE="ID_977051135" STARTARROW="None" STARTINCLINATION="424;27;"/>
@ -101428,7 +101470,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="ksmiletris"/>
</node>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1701900872050" ID="ID_693907147" MODIFIED="1701900908502" TEXT="Exception &quot;Promise already satisfied&quot;">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1701900872050" FOLDED="true" ID="ID_693907147" MODIFIED="1701900908502" TEXT="Exception &quot;Promise already satisfied&quot;">
<icon BUILTIN="broken-line"/>
<node CREATED="1701900913850" ID="ID_1004033455" MODIFIED="1701900920193" TEXT="sichtbar nach dem ersten Bugfix"/>
<node CREATED="1701900969591" ID="ID_1945329018" MODIFIED="1701900982346" TEXT="tritt auf unmittelbar nach dem Planen des 1.Chunk"/>
@ -101453,7 +101495,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="button_ok"/>
</node>
</node>
<node CREATED="1701879543468" ID="ID_1818772058" MODIFIED="1701879550999" TEXT="Beobachtung Scheduler-Gesamtverhalten">
<node COLOR="#435e98" CREATED="1701879543468" ID="ID_1818772058" MODIFIED="1701990872492" TEXT="Beobachtung Scheduler-Gesamtverhalten">
<icon BUILTIN="info"/>
<node COLOR="#338800" CREATED="1701879552131" ID="ID_1345942591" MODIFIED="1701879620762" TEXT="gemeinsame Time-Basis im DUMP-Log herstellen">
<richcontent TYPE="NOTE"><html>
<head/>
@ -101479,14 +101522,15 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</p>
</body>
</html></richcontent>
<arrowlink COLOR="#df2e4b" DESTINATION="ID_1285649422" ENDARROW="Default" ENDINCLINATION="67;0;" ID="Arrow_ID_1426953607" STARTARROW="None" STARTINCLINATION="-2;38;"/>
<arrowlink COLOR="#df2e4b" DESTINATION="ID_1285649422" ENDARROW="Default" ENDINCLINATION="53;2;" ID="Arrow_ID_1426953607" STARTARROW="None" STARTINCLINATION="2;30;"/>
</node>
</node>
<node CREATED="1701879839628" ID="ID_576613689" MODIFIED="1701879852847" TEXT="wir bekommen hier 16 Level -&gt; 16ms"/>
</node>
<node CREATED="1701881012424" ID="ID_1173195975" MODIFIED="1701881019723" TEXT="einige Jobs laufen nicht">
<node CREATED="1701881022535" ID="ID_1285649422" MODIFIED="1701881051273" TEXT="Vermutung: die werden durch fehlende Dependencies geblockt">
<linktarget COLOR="#df2e4b" DESTINATION="ID_1285649422" ENDARROW="Default" ENDINCLINATION="67;0;" ID="Arrow_ID_1426953607" SOURCE="ID_1372474269" STARTARROW="None" STARTINCLINATION="-2;38;"/>
<node COLOR="#435e98" CREATED="1701881012424" ID="ID_1173195975" MODIFIED="1701990880813" TEXT="einige Jobs laufen nicht">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1701881022535" ID="ID_1285649422" MODIFIED="1701990931283" TEXT="Vermutung: die werden durch fehlende Dependencies geblockt">
<linktarget COLOR="#df2e4b" DESTINATION="ID_1285649422" ENDARROW="Default" ENDINCLINATION="53;2;" ID="Arrow_ID_1426953607" SOURCE="ID_1372474269" STARTARROW="None" STARTINCLINATION="2;30;"/>
</node>
<node CREATED="1701881068248" ID="ID_1827795881" MODIFIED="1701881077251" TEXT="in einem Einzelfall best&#xe4;tigt">
<node CREATED="1701881078359" ID="ID_1962795672" MODIFIED="1701882948416" TEXT="der Nachfolger wird gescheduled, nachdem der Vorl&#xe4;ufer bereits aktiviert wurde">