ProcDispatcher: consider and document the fine points of operational semantics

there are some pitfalls related to timing and state,
especially since some state changes are triggered, but not immediately reached
This commit is contained in:
Fischlurch 2016-12-16 23:11:19 +01:00
parent 8ee08905b3
commit 53ed0e9aa3
5 changed files with 163 additions and 4 deletions

View file

@ -46,6 +46,8 @@ using std::unique_ptr;
namespace proc {
namespace control {
namespace error = lumiera::error;
class DispatcherLoop
: ThreadJoinable
, public CommandDispatch
@ -130,6 +132,12 @@ namespace control {
UNIMPLEMENTED("*must* notify loop thread"); /////////////////TODO really?
}
void
awaitCheckpoint()
{
Lock blockWaiting(this, &DispatcherLoop::stateIsSynched);
}
private:
void
run (Subsys::SigTerm sigTerm)
@ -169,6 +177,18 @@ namespace control {
looper_.getTimeout());
}
bool
stateIsSynched()
{
bool duelyResolved = false;
UNIMPLEMENTED("find out if all pending state changes are carried out.");
if (not duelyResolved and calledFromWithinSessionThread())
throw error::Fatal("Possible Deadlock. "
"Attempt to synchronise to a command processing check point "
"from within the (single) session thread."
, error::LUMIERA_ERROR_LIFECYCLE);
}
void
processCommands()
{
@ -180,6 +200,15 @@ namespace control {
{
UNIMPLEMENTED ("start the Proc-Builder to recalculate render nodes network");
}
bool
calledFromWithinSessionThread()
{
UNIMPLEMENTED ("how to find out when the session thread attempts to catch its own tail...???");
////////////////////////////////////////////////////////////////TODO any idea how to achieve that? The lock does not help us, since it is recursive and
//////////////////////////////////////////////////////////////// ... since command/builder execution itself is not performed in a locked section.
//////////////////////////////////////////////////////////////// ... Possibly we'll just have to plant a ThreadLocal to mark this dangerous situation.
}
};
@ -262,6 +291,21 @@ namespace control {
if (runningLoop_)
runningLoop_->deactivateCommandProecssing();
}
/** block until the dispatcher has actually reached disabled state.
* @warning beware of invoking this function from within the session thread,
* since the waiting relies on the very lock also used to coordinate
* command processing and builder runs within that thread.
* @throw error::Fatal when a deadlock due to such a recursive call can be detected
*/
void
ProcDispatcher::awaitDeactivation()
{
Lock sync(this);
if (runningLoop_)
runningLoop_->awaitCheckpoint();
}
void

View file

@ -76,6 +76,7 @@ namespace control {
void activate();
void deactivate();
void awaitDeactivation();
void clear();

View file

@ -166,9 +166,13 @@ namespace session {
}
/**
* @warning throws error::Fatal or even deadlocks when called from session thread
*/
void
commandLogCheckpoint() override
{ //////////////////////////////////////// TICKET #697
control::ProcDispatcher::instance().awaitDeactivation();
INFO (command, " Session shutdown. Command processing stopped.");
}
@ -241,6 +245,7 @@ namespace session {
/** Shut down the current session together with all associated services.
* @todo avoid blocking when aborting render processes ///////////// TICKET #201
* @todo well defined transactional behaviour ///////////////////// TICKET #698
* @warning throws error::Fatal or even deadlocks when called from session thread
*/
void
SessManagerImpl::close()
@ -255,6 +260,7 @@ namespace session {
/** @todo error handling, how to deal with a partially configured session?
* @todo for \c reset() to work, we need to change the implementation of
* AssetManager so support this kind of transactional switch!
* @warning throws error::Fatal or even deadlocks when called from session thread
*/
void
SessManagerImpl::reset()
@ -265,7 +271,9 @@ namespace session {
lifecycle_->pullUp();
}
/**
* @warning throws error::Fatal or even deadlocks when called from session thread
*/
void
SessManagerImpl::load()
{

View file

@ -5222,7 +5222,7 @@ Besides, they provide an __inward interface__ for the [[ProcNode]]s, enabling th
[img[Asset Classess|uml/fig131077.png]]
{{red{Note 3/2010}}} it is very unlikely we'll organise the processing nodes as a class hierarchy. Rather it looks like we'll get several submodules/special capabilities configured in within the Builder</pre>
</div>
<div title="ProcDispatcher" creator="Ichthyostega" modifier="Ichthyostega" created="201612140406" modified="201612151859" tags="def spec SessionLogic draft" changecount="3">
<div title="ProcDispatcher" creator="Ichthyostega" modifier="Ichthyostega" created="201612140406" modified="201612162121" tags="def spec SessionLogic draft" changecount="4">
<pre>//The guard and coordinator of any operation within the session subsystem.//
The session and related components work effectively single threaded. Any tangible operation on the session data structure has to be enqueued as [[command|CommandHandling]] into the dispatcher. Moreover, the [[Builder]] is triggered from the ProcDispatcher; and while the Builder is running, any command processing is halted. The Builder in turn creates or reshapes the processing nodes network, and the changed network is brought into operation with a //transactional switch// -- while render processes on this processing network operate unaffected and essentially multi-threaded.
@ -5251,6 +5251,21 @@ To function properly as action coordinator of the session subsystem, the dispatc
:* ensure they //match// current session, discard obsoleted requests
:* //aggregate// similar requests
:* //supersede// by newer commands of a certain kind
!Operational semantics
The ProcDispatcher is a component with //running state.// There is some kind of working loop, which possibly enters a sleep state when idle. In fact, this loop is executed ''exclusively in the session thread''. This is the very essence of treating the session entirely single threaded, thus evading all the complexities of parallelism. Consequently, the session thread will either
* execute a command on the session
* perform the [[Builder]]
* evaluate loop control logic in the ProcDispatcher
* block waiting in the ProcDispatcher
Initially the command queue is empty and the ProcDispatcher can be considered idle. Whenever more commands are available in the queue, the dispatcher will handle them one after another, without delay, until the queue is emptied. Yet the Builder run need to be kept in mind. Essentially, the builder models a //dirty state:// whenever a command has touched the session, the corresponding LowLevelModel must be considered out of sync, possibly not reflecting the intended semantics of the session anymore. From a strictly logical view angle, we'd need to trigger the builder after each and every session command -- but it was a very fundamental design decision in Lumiera to allow for a longer running build process, more akin to running a compiler. This decision opens all the possibilities of integrating a knowledge based system and resolution activities to find a solution to match the intended session semantics. For this reason, we decouple the UI actions from session and render engine consistency, and we enqueue session commands, to throttle down the number of builder runs.
So the logic to trigger builder runs has to take some leeway into account. Due to the typical interactive working style of an editing application, session commands might be trickling in in strikes of similar commands, intermingled with tiny pauses. For this reason, the ProcDispatcher implements some //hysteresis,// as far as triggering the builder runs is concerned. The builder is fired in idle state, but only after passing some //latency period.// On the other hand, massive UI activities (especially during a builder run) may have flooded the queue, thus sending the session into an extended period of command processing. From the user's view angle, the application looks non responsive in such a case, albeit not frozen, since the UI can still enqueue further commands and thus retains the ability to react locally on user interaction. To mitigate this problem, the builder should be started anyway after some extended period of command processing, even if the queue is not yet emptied. Each builder run produces a structural diff message sent towards the UI and thus causes user visible changes within the session's UI representation. This somewhat stuttering response conveys to the user a tangible sensation of ongoing activity, while communicating at the same time, at least subconsciously some degree of operational overload.
Any change to the circumstances determining the ProcDispatcher's behaviour need to be imparted actively through the public interface -- the dispatcher is not designed to be a state listener or observer. Any such state change notifications are synchronised and cause a wakeup notification to the session thread. For this purpose, enqueuing of further commands counts as state change and is lock protected. Beyond that, any other activities, like //processing// of commands or builder runs, are performed within the session thread without blocking other threads; the locking on the ProcDispatcher is only ever short term to ensure consistent internal state. Clients need to be prepared for the effect of actions to appear asynchronously and with some delay. Especially this means that session switch or shutdown has to await completion of any session command or builder run currently in progress.
When the session is closed or dismantled, further processing in the ProcDispatcher will be disabled, after completing the current command or builder run. This disabled state can be reversed when a new session instance becomes operative. And while the dispatcher will then continue to empty the command queue, most commands in queue will probably be obsoleted and dropped, because of referring to a deceased session instance. Moreover, the lifecycle of the session instances has to be distinguished from the lifecycle of the &quot;session subsystem&quot; as such. When the latter is terminated, be it by a fatal error in some builder run, or be it due to general shutdown of the application, the ProcDispatcher will be asked to terminate the session thread after completing the current activity in progress. Such an event will also discard any further commands waiting in the dispatcher's queue.
</pre>
</div>
<div title="ProcLayer" modifier="Ichthyostega" created="200708100333" modified="200708100338" tags="def">

View file

@ -11016,7 +11016,8 @@
</node>
</node>
<node CREATED="1481778538165" HGAP="37" ID="ID_522398894" MODIFIED="1481778549049" TEXT="ProcDispatcher" VSHIFT="12">
<node CREATED="1481826274018" ID="ID_1916296972" MODIFIED="1481826277691" TEXT="Requirements">
<node CREATED="1481826274018" HGAP="18" ID="ID_1916296972" MODIFIED="1481917919910" TEXT="Requirements" VSHIFT="-7">
<icon BUILTIN="button_ok"/>
<node CREATED="1481826329819" ID="ID_1238318698" MODIFIED="1481826431105" TEXT="enqueue commands concurrently">
<node CREATED="1481827297122" ID="ID_860095678" MODIFIED="1481827311268" TEXT="FIFO for regular commands"/>
<node CREATED="1481827312024" ID="ID_850193431" MODIFIED="1481827318187" TEXT="LIFO for priority requests"/>
@ -11041,6 +11042,88 @@
<node CREATED="1481827652539" ID="ID_313823253" MODIFIED="1481827657846" TEXT="supersede"/>
</node>
</node>
<node CREATED="1481917572639" HGAP="74" ID="ID_190109346" MODIFIED="1481917906139" TEXT="operational" VSHIFT="-27">
<icon BUILTIN="pencil"/>
<node CREATED="1481917612602" ID="ID_567971228" MODIFIED="1481917622388" TEXT="Loop l&#xe4;uft stets, aber blockt ggfs"/>
<node CREATED="1481917641910" ID="ID_1157396234" MODIFIED="1481917663639" TEXT="anf&#xe4;nglich idle --&gt; schlafen"/>
<node CREATED="1481918441020" ID="ID_584625018" MODIFIED="1481918463933" TEXT="Command-Queue wird ohne Verz&#xf6;gerung abgearbeitet"/>
<node CREATED="1481918465680" ID="ID_973127249" MODIFIED="1481918474355" TEXT="Builder startet">
<node CREATED="1481918475295" ID="ID_1997533333" MODIFIED="1481918480586" TEXT="nach kurzer Idle-Periode"/>
<node CREATED="1481918481102" ID="ID_335740959" MODIFIED="1481918491976" TEXT="nach l&#xe4;ngerer Command-Abarbeitung"/>
</node>
<node CREATED="1481917691047" ID="ID_714937554" MODIFIED="1481917700266" TEXT="&#xc4;nderung der Umst&#xe4;nde">
<node CREATED="1481917700998" ID="ID_470084004" MODIFIED="1481917705641" TEXT="kommt stets &#xfc;ber Interface"/>
<node CREATED="1481917726683" ID="ID_962119246" MODIFIED="1481917738197" TEXT="weckt den Loop-Thread auf"/>
<node CREATED="1481917766837" ID="ID_1794908304" MODIFIED="1481918114857" TEXT="ist atomar (locking)">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Guard beim Zugang &#252;ber das Interface
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1481918074269" ID="ID_1984339005" MODIFIED="1481918091136">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
<i>nur sie</i>&#160;ist atomar
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
<node CREATED="1481918117831" ID="ID_924462902" MODIFIED="1481918121154" TEXT="asynchron">
<node CREATED="1481918130245" ID="ID_1624017067" MODIFIED="1481918382409">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
<b>nur ein Thread</b>&#160;f&#252;r Commands und Builder
</p>
</body>
</html>
</richcontent>
<arrowlink COLOR="#171ccb" DESTINATION="ID_1214851922" ENDARROW="Default" ENDINCLINATION="53;-5;" ID="Arrow_ID_1745317422" STARTARROW="None" STARTINCLINATION="42;2;"/>
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1481918174199" ID="ID_364234189" MODIFIED="1481918180762" TEXT="essentiell f&#xfc;r Konsitenz der Session"/>
<node CREATED="1481918181254" ID="ID_1313994065" MODIFIED="1481918191233" TEXT="stellt bereits sicher, da&#xdf; der Builder blockt"/>
</node>
<node CREATED="1481918194356" ID="ID_1233041580" MODIFIED="1481918206823" TEXT="einf&#xfc;gen neuer Commands"/>
<node CREATED="1481918273490" ID="ID_1214851922" MODIFIED="1481918368739" TEXT="n&#xe4;chstes Command starten">
<arrowlink COLOR="#010de6" DESTINATION="ID_1857790160" ENDARROW="Default" ENDINCLINATION="86;0;" ID="Arrow_ID_1735750125" STARTARROW="Default" STARTINCLINATION="127;3;"/>
<linktarget COLOR="#171ccb" DESTINATION="ID_1214851922" ENDARROW="Default" ENDINCLINATION="53;-5;" ID="Arrow_ID_1745317422" SOURCE="ID_1624017067" STARTARROW="None" STARTINCLINATION="42;2;"/>
</node>
<node CREATED="1481918291351" ID="ID_1857790160" MODIFIED="1481918331776" TEXT="Builder-Lauf initiieren">
<linktarget COLOR="#010de6" DESTINATION="ID_1857790160" ENDARROW="Default" ENDINCLINATION="86;0;" ID="Arrow_ID_1735750125" SOURCE="ID_1214851922" STARTARROW="Default" STARTINCLINATION="127;3;"/>
</node>
<node CREATED="1481918212722" ID="ID_1376599707" MODIFIED="1481918221844" TEXT="Shutdown-Triggger"/>
<node CREATED="1481918222577" ID="ID_1588368762" MODIFIED="1481918245517" TEXT="Sperre setzen">
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
<node CREATED="1481918517018" HGAP="11" ID="ID_916331172" MODIFIED="1481918522478" TEXT="Sperre" VSHIFT="8">
<node CREATED="1481918524673" ID="ID_1705856370" MODIFIED="1481918546498" TEXT="verhindert Command- und Builder-start"/>
<node CREATED="1481918547182" ID="ID_920627724" MODIFIED="1481920718126" TEXT="bestehende Commands / Builder-L&#xe4;ufe werden noch abgeschlossen"/>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1481920722100" ID="ID_1378897740" MODIFIED="1481920732028" TEXT="SessionManager mu&#xdf; auf Sperre warten">
<icon BUILTIN="flag-pink"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1481926040310" ID="ID_667460598" MODIFIED="1481926061115" TEXT="Deadlock wenn der Session-Thread selber auf diese Sperre wartet">
<icon BUILTIN="flag-pink"/>
</node>
</node>
</node>
<node CREATED="1481778549940" HGAP="35" ID="ID_676848252" MODIFIED="1481828600299" TEXT="DispatcherLoop">
<icon BUILTIN="pencil"/>
<node CREATED="1481778741546" ID="ID_684867533" MODIFIED="1481778755694" TEXT="&#xf6;ffnet Session-Interface">
@ -11091,7 +11174,15 @@
</node>
<node CREATED="1481778590790" ID="ID_1748096848" MODIFIED="1481826297245" TEXT="Anhalten">
<linktarget COLOR="#f70841" DESTINATION="ID_1748096848" ENDARROW="Default" ENDINCLINATION="-353;-77;" ID="Arrow_ID_506637666" SOURCE="ID_1844558240" STARTARROW="None" STARTINCLINATION="-200;38;"/>
<node CREATED="1481778666820" ID="ID_1860590145" MODIFIED="1481778688685" TEXT="trigger-Variable mustHalt_"/>
<node CREATED="1481778666820" ID="ID_1860590145" MODIFIED="1481917471448" TEXT="Trigger-Variable im Looper">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1481917462421" ID="ID_200909201" MODIFIED="1481917474864" TEXT="shutdown-Trigger ebenda">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1481917478412" ID="ID_594556769" MODIFIED="1481917505131" TEXT="kombiniert enabled und shutdown">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1481778708207" HGAP="48" ID="ID_1311208726" MODIFIED="1481831819823" TEXT="Builder-Steuerung" VSHIFT="-5">