2009-06-08 04:46:07 +02:00
/*
ProcDispatcher - Proc - Layer command dispatch and execution
2010-12-17 23:28:49 +01:00
2009-06-08 04:46:07 +02:00
Copyright ( C ) Lumiera . org
2008 , Hermann Vosseler < Ichthyostega @ web . de >
2010-12-17 23:28:49 +01:00
2009-06-08 04:46:07 +02:00
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License as
2010-12-17 23:28:49 +01:00
published by the Free Software Foundation ; either version 2 of
the License , or ( at your option ) any later version .
2009-06-08 04:46:07 +02:00
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 .
2010-12-17 23:28:49 +01:00
2009-06-08 04:46:07 +02:00
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 0213 9 , USA .
2010-12-17 23:28:49 +01:00
2009-06-08 04:46:07 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2016-12-16 23:26:56 +01:00
/** @file proc-dispatcher.cpp
* * Implementation details of running commands and the builder .
2016-12-22 19:35:42 +01:00
* * The ProcDispatcher is at the heart of the session subsystem and implements a
* * ( single ) session thread to perform commands and trigger builder runs . New commands
* * can be enqueued with a dedicated CommandQueue , while the details of operation control
* * logic are encapsulated in a [ logic component ] ( \ ref Looper ) .
* *
* * # Operational Semantics
* * We need to distinguish between the ProcDispatcher itself , which is a static ( singleton ) service ,
* * and the » Session Subsystem « plus the _Session proper . _ The subsystem has an application - global lifecycle ,
* * while the Session itself is a data structure and can be closed , opened or re - loaded . There is a singular
* * transactional access point to the Session datastructure , which can be switched to new session contents .
* * But external manipulation of the session contents is performed by commands , which are _dispatched_ - -
* * to manage this process is the concern of the » Session Subsystem « .
* *
* * Closing a session blocks further command processing , while the lifecycle of the _Session Subsystem_ is
* * actually linked to _running the \ ref DispatcherLoop_ - - implementation logic defined in this translation
* * unit here . This loop implementation is performed in a dedicated thread , _the Session Loop Thread . _ And
* * this also entails opening the public SessionCommandService interface .
* *
* * # # Loop operation control
2016-12-22 21:36:03 +01:00
* * The loop starts with a blocking wait state , bound to the condition Looper : : requireAction . Here , Looper
* * is a helper to encapsulate the control logic , separated from the actual control flow . In the loop body ,
* * depending on the Looper ' s decision , either the next command is fetched from the CommandQueue and dispatched ,
2016-12-23 07:26:00 +01:00
* * or a builder run is triggered , rebuilding the » Low - Level - Model « to reflect the executed command ' s effects .
2016-12-22 21:36:03 +01:00
* * After these working actions , a _ " check point " _ is reached in Looper : : markStateProcessed , which updates
2016-12-23 07:26:00 +01:00
* * the logic and manages a _dirty state_ to control builder runs . After that , the looping control flow
* * again enters the possibly blocking condition wait .
2016-12-22 21:36:03 +01:00
* * - after a command has been dispatched , the builder is _dirty_ and needs to run
* * - yet we continue to dispatch further commands , until the queue is emptied
* * - and only after a further small latency wait , the builder run is triggered
* * - but we _enforce a builder run_ after some extended timeout period , even
* * when the command queue is not yet emptied
* * - from the outside , it is possible to deactivate processing and place the
* * loop into dormant state . This is used while closing or loading the Session
* * - and of course we can request the Session Loop Thread to stop , for shutting
* * down the » Session Subsystem « as a whole
* * - in both cases the currently performed action ( command or builder ) is
* * finished , without interrupt
2016-12-22 19:35:42 +01:00
* *
2016-12-22 21:36:03 +01:00
* * # # Locking
* * The ProcDispatcher uses an " inner and outer capsule " design , and both layers are locked independently .
* * On the outer layer , locking ensures sanity of the control data structures , while locking on the inner
* * layer guards the communication with the Session Loop Thread , and coordinates sleep wait and notification .
* * As usual with Lumiera ' s Thread wrapper , the management of the thread ' s lifecycle itself , hand - over of
2016-12-23 07:26:00 +01:00
* * parameters , and starting / joining of the thread operation is protected by separate locking embedded
* * into the thread and threadpool handling code .
* * @ note most of the time , the Session Loop Thread does not hold any lock , most notably while performing
* * a command or running the builder . Likewise , evaluation of the control logic in the Looper helper
2016-12-22 21:36:03 +01:00
* * is a private detail of the performing thread . The lock is acquired solely for checking or leaving
2017-01-05 21:40:37 +01:00
* * the wait state and when fetching the next command from queue .
2016-12-16 23:26:56 +01:00
* *
* * @ see ProcDispatcher
* * @ see DispatcherLooper_test
* * @ see CommandQueue_test
* *
*/
2009-06-08 04:46:07 +02:00
2016-12-13 04:45:00 +01:00
# include "lib/error.hpp"
# include "include/logging.h"
2009-06-08 04:46:07 +02:00
# include "proc/control/proc-dispatcher.hpp"
2016-12-15 05:21:03 +01:00
# include "proc/control/command-dispatch.hpp"
2016-12-15 20:48:35 +01:00
# include "proc/control/command-queue.hpp"
# include "proc/control/looper.hpp"
2016-12-15 05:38:12 +01:00
# include "proc/control/session-command-service.hpp"
2009-09-29 04:47:09 +02:00
# include "proc/mobject/session.hpp"
2016-12-13 04:34:28 +01:00
# include "backend/thread-wrapper.hpp"
2009-06-08 04:46:07 +02:00
2016-12-15 05:54:48 +01:00
# include <memory>
2016-12-13 04:34:28 +01:00
2016-12-14 04:18:58 +01:00
using lib : : Sync ;
using lib : : RecursiveLock_Waitable ;
2017-01-05 23:36:42 +01:00
using backend : : Thread ;
2016-12-15 05:54:48 +01:00
using std : : unique_ptr ;
2009-06-08 04:46:07 +02:00
2011-12-02 17:50:44 +01:00
namespace proc {
2009-06-08 04:46:07 +02:00
namespace control {
2016-12-16 23:11:19 +01:00
namespace error = lumiera : : error ;
2017-01-05 23:36:42 +01:00
/********************************************************************/ /**
* PImpl within ProcDispatcher to implement the _Session Loop Thread . _
* During the lifetime of this object . . .
2016-12-23 07:26:00 +01:00
* - the SessionCommandService is offered to enqueue commands
* - the Session Loop thread dispatches commands and triggers the Builder
* @ see DispatcherLooper_test
*/
2016-12-13 04:34:28 +01:00
class DispatcherLoop
2017-01-05 02:00:35 +01:00
: Thread
2016-12-15 05:21:03 +01:00
, public CommandDispatch
2016-12-14 04:18:58 +01:00
, public Sync < RecursiveLock_Waitable >
2016-12-13 04:34:28 +01:00
{
2017-01-05 23:36:42 +01:00
2016-12-22 19:35:42 +01:00
/** manage the primary public Session interface */
2016-12-15 05:54:48 +01:00
unique_ptr < SessionCommandService > commandService_ ;
2016-12-15 05:38:12 +01:00
2016-12-15 20:48:35 +01:00
CommandQueue queue_ ;
Looper looper_ ;
2016-12-15 05:38:12 +01:00
2016-12-13 04:34:28 +01:00
public :
2017-01-04 01:44:35 +01:00
/** start the session loop thread
* @ param notification callback to invoke on thread termination
* @ remark _in theory_ this ctor could block , since it waits for the thread
* actually to get operational and it waits for the SessionCommand interface
2017-01-05 22:35:33 +01:00
* to be opened . The latter _better should not_ run into any obstacles , because
* in case it does , the main application thread will be deadlocked on startup .
2017-01-04 01:44:35 +01:00
* Such might happen indirectly , when something depends on " the Session "
*/
2016-12-13 04:34:28 +01:00
DispatcherLoop ( Subsys : : SigTerm notification )
2017-01-05 02:00:35 +01:00
: Thread { " Lumiera Session " , bind ( & DispatcherLoop : : runSessionThread , this , notification ) }
2017-01-05 22:35:33 +01:00
, commandService_ ( )
2016-12-15 20:48:35 +01:00
, queue_ ( )
2016-12-16 19:21:06 +01:00
, looper_ ( [ & ] ( ) - > bool
{
return not queue_ . empty ( ) ;
} )
2016-12-13 04:34:28 +01:00
{
2017-01-05 22:35:33 +01:00
Thread : : sync ( ) ; // done with setup; loop may run now....
2016-12-13 04:34:28 +01:00
INFO ( session , " Proc-Dispatcher running... " ) ;
2017-01-05 22:35:33 +01:00
{
Lock ( this ) ; // open public session interface:
commandService_ . reset ( new SessionCommandService ( * this ) ) ;
}
2016-12-13 04:34:28 +01:00
}
~ DispatcherLoop ( )
{
2016-12-15 05:54:48 +01:00
try {
2017-01-05 02:00:35 +01:00
commandService_ . reset ( ) ; // redundant call, to ensure session interface is closed reliably
2016-12-15 05:54:48 +01:00
INFO ( session , " Proc-Dispatcher stopped. " ) ;
}
ERROR_LOG_AND_IGNORE ( session , " Stopping the Proc-Dispatcher " ) ;
2016-12-13 04:34:28 +01:00
}
2016-12-14 04:57:08 +01:00
void
activateCommandProecssing ( )
{
Lock sync ( this ) ;
2017-01-05 23:36:42 +01:00
looper_ . enableProcessing ( true ) ;
2016-12-14 04:57:08 +01:00
INFO ( command , " Session command processing activated. " ) ;
2017-01-05 23:36:42 +01:00
sync . notifyAll ( ) ;
2016-12-14 04:57:08 +01:00
}
void
deactivateCommandProecssing ( )
{
Lock sync ( this ) ;
2017-01-05 23:36:42 +01:00
looper_ . enableProcessing ( false ) ;
2016-12-14 04:57:08 +01:00
INFO ( command , " Session command interface closed. " ) ;
2017-01-05 23:36:42 +01:00
sync . notifyAll ( ) ;
2016-12-14 04:57:08 +01:00
}
2016-12-15 05:21:03 +01:00
void
2017-01-05 20:43:53 +01:00
requestStop ( ) noexcept
2016-12-15 05:21:03 +01:00
{
Lock sync ( this ) ;
2017-01-05 20:43:53 +01:00
commandService_ . reset ( ) ; // closes Session interface
looper_ . triggerShutdown ( ) ;
2017-01-05 21:40:37 +01:00
sync . notifyAll ( ) ;
2016-12-15 05:21:03 +01:00
}
2016-12-23 23:42:27 +01:00
void
2017-01-05 21:40:37 +01:00
awaitStateProcessed ( ) const
2016-12-23 23:42:27 +01:00
{
2017-01-05 23:36:42 +01:00
Lock blockWaiting ( unConst ( this ) , & DispatcherLoop : : stateIsSynched ) ; ///////////////////////TICKET #1057 : const correctness on wait predicate
// wake-up typically by updateState()
2016-12-23 23:42:27 +01:00
}
2016-12-15 05:21:03 +01:00
size_t
size ( ) const
{
2016-12-25 22:26:16 +01:00
Lock sync ( this ) ;
return queue_ . size ( ) ;
2016-12-15 05:21:03 +01:00
}
2017-01-05 20:43:53 +01:00
/* === CommandDispatch interface === */
2016-12-15 06:21:59 +01:00
void
2017-01-05 20:43:53 +01:00
enqueue ( Command cmd ) override
2016-12-15 06:21:59 +01:00
{
Lock sync ( this ) ;
2017-01-05 20:43:53 +01:00
queue_ . feed ( cmd ) ;
2017-01-05 21:40:37 +01:00
sync . notifyAll ( ) ;
2016-12-15 06:21:59 +01:00
}
2016-12-16 23:11:19 +01:00
void
2017-01-05 20:43:53 +01:00
clear ( ) override
2016-12-16 23:11:19 +01:00
{
2017-01-05 20:43:53 +01:00
Lock sync ( this ) ;
queue_ . clear ( ) ;
2017-01-05 21:40:37 +01:00
sync . notifyAll ( ) ;
2016-12-16 23:11:19 +01:00
}
2016-12-13 04:34:28 +01:00
private :
2016-12-22 04:04:41 +01:00
/**
2017-01-05 20:43:53 +01:00
* any operation running in the Session thread is
* started from here . When this loop terminates ,
* the » session subsystem « shuts down .
2016-12-22 04:04:41 +01:00
*/
2016-12-13 04:34:28 +01:00
void
2017-01-05 20:43:53 +01:00
runSessionThread ( Subsys : : SigTerm notifyEnd )
2016-12-13 04:34:28 +01:00
{
2016-12-13 04:45:00 +01:00
string errorMsg ;
2016-12-22 18:42:12 +01:00
syncPoint ( ) ;
2016-12-13 04:45:00 +01:00
try
{
2016-12-15 22:15:20 +01:00
while ( looper_ . shallLoop ( ) )
{
awaitAction ( ) ;
2016-12-22 21:36:03 +01:00
if ( looper_ . isDying ( ) )
break ;
2016-12-21 03:15:36 +01:00
if ( looper_ . runBuild ( ) )
2016-12-15 22:15:20 +01:00
startBuilder ( ) ;
else
if ( looper_ . isWorking ( ) )
processCommands ( ) ;
2016-12-22 21:36:03 +01:00
updateState ( ) ;
2016-12-15 22:15:20 +01:00
}
2016-12-13 04:45:00 +01:00
}
catch ( lumiera : : Error & problem )
{
errorMsg = problem . what ( ) ;
lumiera_error ( ) ; // clear error flag
}
catch ( . . . )
{
errorMsg = string { lumiera_error ( ) } ;
}
2017-01-05 20:43:53 +01:00
// leave the Session thread...
// send notification of subsystem shutdown
notifyEnd ( & errorMsg ) ;
2016-12-13 04:34:28 +01:00
}
2016-12-15 22:15:20 +01:00
void
2016-12-22 21:36:03 +01:00
awaitAction ( ) ///< at begin of loop body...
2016-12-15 22:15:20 +01:00
{
2016-12-16 19:21:06 +01:00
Lock ( this ) . wait ( looper_ , & Looper : : requireAction ,
2016-12-15 22:15:20 +01:00
looper_ . getTimeout ( ) ) ;
}
2016-12-22 21:36:03 +01:00
void
updateState ( ) ///< at end of loop body...
{
looper_ . markStateProcessed ( ) ;
2017-01-05 23:36:42 +01:00
if ( looper_ . isDisabled ( ) ) // otherwise wake-up would not be safe
2016-12-22 21:36:03 +01:00
Lock ( this ) . notifyAll ( ) ;
}
2016-12-16 23:11:19 +01:00
bool
stateIsSynched ( )
{
2017-01-05 23:36:42 +01:00
if ( calledFromWithinSessionThread ( ) )
2016-12-16 23:11:19 +01:00
throw error : : Fatal ( " Possible Deadlock. "
" Attempt to synchronise to a command processing check point "
" from within the (single) session thread. "
, error : : LUMIERA_ERROR_LIFECYCLE ) ;
2016-12-21 03:15:36 +01:00
return not looper_ . hasPendingChanges ( ) ;
2016-12-16 23:11:19 +01:00
}
2016-12-15 22:15:20 +01:00
void
processCommands ( )
{
2017-01-05 20:43:53 +01:00
Command cmd ;
{
Lock sync ( this ) ;
if ( not queue_ . empty ( ) )
cmd = queue_ . pop ( ) ;
}
if ( cmd )
{
TODO ( " +++ dispatch %s " , util : : cStr ( cmd ) ) ;
}
2016-12-15 22:15:20 +01:00
}
void
startBuilder ( )
{
2017-01-05 20:43:53 +01:00
TODO ( " +++ start the Proc-Builder... " ) ;
2016-12-15 22:15:20 +01:00
}
2016-12-16 23:11:19 +01:00
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.
2016-12-20 02:35:45 +01:00
///////////////////////////////////////////////////////////////////////////////TICKET #1054 : can be done by relying on some internals of our thread handling framework
2016-12-16 23:11:19 +01:00
}
2016-12-13 04:34:28 +01:00
} ;
2009-09-29 04:47:09 +02:00
2016-12-15 22:15:20 +01:00
2017-01-05 23:36:42 +01:00
2009-09-29 04:47:09 +02:00
/** storage for Singleton access */
2013-10-20 03:19:36 +02:00
lib : : Depend < ProcDispatcher > ProcDispatcher : : instance ;
2009-09-29 04:47:09 +02:00
2017-01-05 23:36:42 +01:00
/* ======== ProcDispatcher implementation ======== */
2016-12-13 04:34:28 +01:00
2017-01-04 01:44:35 +01:00
/** starting the ProcDispatcher means to start the session subsystem.
* @ return ` false ` when _starting_ failed since it is already running . . .
* @ remark this function implements the start operation for the » session subsystem « .
* More specifically , this operation starts a new thread to perform the
* _session loop , _ which means to perform commands and trigger the builder .
* It might block temporarily for synchronisation with this new thread and
* while opening the SessionCommand facade .
*/
2016-12-13 04:34:28 +01:00
bool
ProcDispatcher : : start ( Subsys : : SigTerm termNotification )
{
2016-12-14 04:18:58 +01:00
Lock sync ( this ) ;
2016-12-13 04:34:28 +01:00
if ( runningLoop_ ) return false ;
runningLoop_ . reset (
new DispatcherLoop (
[ = ] ( string * problemMessage )
{
2017-01-05 03:38:46 +01:00
ProcDispatcher : : endRunningLoopState ( ) ;
2016-12-13 04:34:28 +01:00
termNotification ( problemMessage ) ;
} ) ) ;
2016-12-14 04:57:08 +01:00
if ( active_ )
runningLoop_ - > activateCommandProecssing ( ) ;
2016-12-13 04:34:28 +01:00
return true ;
}
2017-01-04 01:44:35 +01:00
/** whether the »session subsystem« is operational.
* @ return ` true ` if the session loop thread has been fully
* started and is not ( yet ) completely terminated .
*/
2016-12-13 04:34:28 +01:00
bool
ProcDispatcher : : isRunning ( )
{
2016-12-14 04:18:58 +01:00
Lock sync ( this ) ;
2016-12-13 04:34:28 +01:00
return bool ( runningLoop_ ) ;
}
2016-12-15 06:21:59 +01:00
/** signal to the loop thread that it needs to terminate.
2017-01-05 03:38:46 +01:00
* @ note the immediate consequence is to close SessionCommandService
2016-12-15 06:21:59 +01:00
*/
2016-12-13 04:34:28 +01:00
void
2016-12-15 06:21:59 +01:00
ProcDispatcher : : requestStop ( ) noexcept
2017-01-05 03:38:46 +01:00
{
try {
Lock sync ( this ) ;
if ( runningLoop_ )
runningLoop_ - > requestStop ( ) ;
}
ERROR_LOG_AND_IGNORE ( command , " Request for Session Loop Thread to terminate " ) ;
}
/** @internal clean-up when leaving the session loop thread.
* This function is hooked up in to the termination callback ,
* and is in fact the only one to delete the loop PImpl . We
* take the ( outer ) lock on ProcDispatcher to ensure no one
* commits anything to the DispatcherLoop object while being
* deleted . The call itself , while technically originating
* from within DispatcherLoop : : runSessionThread ( ) , relies
* solely on stack based context data and is a tail call .
*/
void
ProcDispatcher : : endRunningLoopState ( )
2016-12-13 04:34:28 +01:00
{
2016-12-14 04:18:58 +01:00
Lock sync ( this ) ;
2016-12-15 06:21:59 +01:00
if ( runningLoop_ )
2017-01-05 03:38:46 +01:00
runningLoop_ . reset ( ) ; // delete DispatcherLoop object
else
WARN ( command , " clean-up of DispatcherLoop invoked, "
" while ProcDispatcher is not marked as 'running'. "
" Likely an error in lifecycle logic, as the only one "
" intended to delete this object is the loop thread itself. " ) ;
2016-12-13 04:34:28 +01:00
}
2016-12-14 04:57:08 +01:00
/** activate processing of enqueued session commands.
* @ remarks command processing serves as public external interface
* to the session . This call is used by the session lifecycle ( SessManagerImpl )
* when the session is brought up ; any other invocation runs danger to mess up
* the session lifecycle state and process commands on a deconfigured session .
* In case the dispatcher loop is not actually running , the activation state
* is stored and applied accordingly later , when the loop is fired up .
*/
2009-09-29 04:47:09 +02:00
void
ProcDispatcher : : activate ( )
{
2016-12-14 04:18:58 +01:00
Lock sync ( this ) ;
2016-12-14 04:57:08 +01:00
active_ = true ;
if ( runningLoop_ )
runningLoop_ - > activateCommandProecssing ( ) ;
2009-09-29 04:47:09 +02:00
}
2017-01-05 23:36:42 +01:00
/** halt further processing of session commands
* @ note the processing itself runs in a separate thread , thus any currently
* ongoing command or builder execution will be completed prior to this setting
* to take effect . If the intention is to halt processing because the session is
* about to dismantled , it is mandatory to awaitDeactivation ( )
*/
2009-09-29 04:47:09 +02:00
void
ProcDispatcher : : deactivate ( )
{
2016-12-14 04:18:58 +01:00
Lock sync ( this ) ;
2016-12-14 04:57:08 +01:00
active_ = false ;
if ( runningLoop_ )
runningLoop_ - > deactivateCommandProecssing ( ) ;
2009-09-29 04:47:09 +02:00
}
2016-12-16 23:11:19 +01:00
/** 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_ )
2016-12-22 21:36:03 +01:00
runningLoop_ - > awaitStateProcessed ( ) ;
2016-12-16 23:11:19 +01:00
}
2009-09-29 04:47:09 +02:00
2017-01-05 03:38:46 +01:00
/** discard any commands waiting in the dispatcher queue */
2009-09-29 04:47:09 +02:00
void
ProcDispatcher : : clear ( )
{
2016-12-14 04:18:58 +01:00
Lock sync ( this ) ;
2016-12-14 04:57:08 +01:00
if ( not empty ( ) )
2016-12-15 05:21:03 +01:00
{
WARN ( command , " DISCARDING pending Session commands. " ) ;
REQUIRE ( runningLoop_ ) ;
runningLoop_ - > clear ( ) ;
}
2009-09-29 04:47:09 +02:00
}
bool
ProcDispatcher : : empty ( ) const
{
2016-12-14 04:18:58 +01:00
Lock sync ( this ) ;
2016-12-15 05:21:03 +01:00
return not runningLoop_
or 0 = = runningLoop_ - > size ( ) ;
2009-09-29 04:47:09 +02:00
}
2009-06-08 04:46:07 +02:00
2011-12-02 17:50:44 +01:00
} } // namespace proc::control