diff --git a/src/proc/control/proc-dispatcher.cpp b/src/proc/control/proc-dispatcher.cpp index fb6d5cadf..ed1b94fe3 100644 --- a/src/proc/control/proc-dispatcher.cpp +++ b/src/proc/control/proc-dispatcher.cpp @@ -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 diff --git a/src/proc/control/proc-dispatcher.hpp b/src/proc/control/proc-dispatcher.hpp index 8ea73de2e..b97e3046e 100644 --- a/src/proc/control/proc-dispatcher.hpp +++ b/src/proc/control/proc-dispatcher.hpp @@ -76,6 +76,7 @@ namespace control { void activate(); void deactivate(); + void awaitDeactivation(); void clear(); diff --git a/src/proc/mobject/session/sess-manager-impl.cpp b/src/proc/mobject/session/sess-manager-impl.cpp index 2b9e0ed68..53a0252a9 100644 --- a/src/proc/mobject/session/sess-manager-impl.cpp +++ b/src/proc/mobject/session/sess-manager-impl.cpp @@ -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() { diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 7b76182c7..26af18a66 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -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 -
+
//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 "session subsystem" 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.
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 9e14607d8..45d0dd11c 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -11016,7 +11016,8 @@ - + + @@ -11041,6 +11042,88 @@ + + + + + + + + + + + + + + + + + + +

+ Guard beim Zugang über das Interface +

+ + +
+
+ + + + + + +

+ nur sie ist atomar +

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

+ nur ein Thread für Commands und Builder +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -11091,7 +11174,15 @@ - + + + + + + + + +