reconsider synchronisation and switch some parts to instance based locks.
This commit is contained in:
parent
e801c324bf
commit
75bdc877dc
9 changed files with 70 additions and 49 deletions
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include "gui/guifacade.hpp"
|
||||
#include "include/guinotificationfacade.h"
|
||||
#include "lib/sync.hpp"
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/singleton.hpp"
|
||||
#include "lib/functorutil.hpp"
|
||||
|
|
@ -42,6 +43,7 @@ namespace gui {
|
|||
using lumiera::Subsys;
|
||||
using lumiera::InstanceHandle;
|
||||
using util::dispatchSequenced;
|
||||
using lib::Sync;
|
||||
|
||||
|
||||
|
||||
|
|
@ -79,7 +81,8 @@ namespace gui {
|
|||
scoped_ptr<GuiRunner> facade (0);
|
||||
|
||||
class GuiSubsysDescriptor
|
||||
: public lumiera::Subsys
|
||||
: public lumiera::Subsys,
|
||||
public Sync<>
|
||||
{
|
||||
operator string () const { return "Lumiera GTK GUI"; }
|
||||
|
||||
|
|
@ -98,7 +101,7 @@ namespace gui {
|
|||
bool
|
||||
start (lumiera::Option&, Subsys::SigTerm termination)
|
||||
{
|
||||
//Lock guard (*this);
|
||||
Lock guard (this);
|
||||
if (facade) return false; // already started
|
||||
|
||||
facade.reset (
|
||||
|
|
@ -119,14 +122,14 @@ namespace gui {
|
|||
bool
|
||||
checkRunningState () throw()
|
||||
{
|
||||
//Lock guard (*this);
|
||||
Lock guard (this);
|
||||
return (facade);
|
||||
}
|
||||
|
||||
void
|
||||
closeGuiModule (lumiera::Error *)
|
||||
{
|
||||
//Lock guard (*this);
|
||||
Lock guard (this);
|
||||
if (!facade)
|
||||
{
|
||||
WARN (operate, "Termination signal invoked, but GUI is currently closed. "
|
||||
|
|
|
|||
|
|
@ -23,19 +23,12 @@
|
|||
|
||||
#include "common/subsys.hpp"
|
||||
|
||||
#include "lib/error.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
|
||||
//using util::isnil;
|
||||
//using util::cStr;
|
||||
|
||||
namespace lumiera {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Subsys::~Subsys() { }
|
||||
|
||||
|
||||
|
|
@ -43,7 +36,6 @@ namespace lumiera {
|
|||
Subsys&
|
||||
Subsys::depends (Subsys& prereq)
|
||||
{
|
||||
TODO ("anything else to care when defining a dependency on the prerequisite subsystem??");/////////////////////TODO
|
||||
prereq_.push_back(&prereq);
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -53,7 +45,6 @@ namespace lumiera {
|
|||
bool
|
||||
Subsys::isRunning()
|
||||
{
|
||||
//Lock guard (this);
|
||||
return checkRunningState();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ namespace lumiera {
|
|||
* Dependencies and lifecycle of a partially independent Subsystem of the Application.
|
||||
* Using such descriptors, AppState as activated from main() is able to pull up,
|
||||
* maintain and shut down the primary parts of the Application.
|
||||
* @note synchronisation is up to the implementor.
|
||||
*/
|
||||
class Subsys
|
||||
: private noncopyable
|
||||
|
|
|
|||
|
|
@ -44,8 +44,16 @@ namespace lumiera {
|
|||
using util::and_all;
|
||||
using util::for_each;
|
||||
using util::removeall;
|
||||
using lib::Sync;
|
||||
using lib::RecursiveLock_Waitable;
|
||||
|
||||
namespace {
|
||||
|
||||
/** limit waiting for subsystem shutdown in case of
|
||||
* an emergency shutdown to max 2 seconds */
|
||||
const uint EMERGENCYTIMEOUT = 2000;
|
||||
|
||||
|
||||
function<bool(Subsys*)>
|
||||
isRunning() {
|
||||
return bind (&Subsys::isRunning, _1);
|
||||
|
|
@ -76,10 +84,9 @@ namespace lumiera {
|
|||
* Usually, the startup process is conducted from one (main) thread, which enters
|
||||
* a blocking wait() after starting the subsystems. Awakened by some termination
|
||||
* signal from one of the subsystems, termination of any remaining subsystems
|
||||
* will be triggered. The wait() function returns after shutdown of all subsystems,
|
||||
* will be triggered. The #wait() function returns after shutdown of all subsystems,
|
||||
* signalling an emergency exit (caused by an exception) with its return value.
|
||||
*
|
||||
* @todo implement an object monitor primitive based on a mutex and a condition. Inherit from this privately and create local instances of the embedded Lock class to activate the locking and wait/notify
|
||||
* @todo maybe use my refArray (see builder) to use Subsys& instead of Subsys* ??
|
||||
*
|
||||
* @see lumiera::AppState
|
||||
|
|
@ -87,7 +94,7 @@ namespace lumiera {
|
|||
* @see main.cpp
|
||||
*/
|
||||
class SubsystemRunner
|
||||
// : Sync
|
||||
: public Sync<RecursiveLock_Waitable>
|
||||
{
|
||||
Option& opts_;
|
||||
volatile bool emergency_;
|
||||
|
|
@ -109,7 +116,7 @@ namespace lumiera {
|
|||
void
|
||||
maybeRun (Subsys& susy)
|
||||
{
|
||||
//Lock guard (this);
|
||||
Lock guard (this);
|
||||
|
||||
if (!susy.isRunning() && susy.shouldStart (opts_))
|
||||
triggerStartup (&susy);
|
||||
|
|
@ -120,23 +127,28 @@ namespace lumiera {
|
|||
void
|
||||
shutdownAll ()
|
||||
{
|
||||
//Lock guard (this);
|
||||
Lock guard (this);
|
||||
for_each (running_, killIt_);
|
||||
}
|
||||
|
||||
void
|
||||
triggerEmergency (bool cond)
|
||||
{
|
||||
Lock guard (this);
|
||||
emergency_ |= cond;
|
||||
}
|
||||
|
||||
bool
|
||||
wait ()
|
||||
{
|
||||
//Lock(this).wait (&SubsystemRunner::allDead);
|
||||
Lock wait_blocking(this, &SubsystemRunner::allDead);
|
||||
return isEmergencyExit();
|
||||
}
|
||||
|
||||
bool isEmergencyExit () { return emergency_; }
|
||||
void triggerEmergency (bool cond) { emergency_ |= cond; }
|
||||
|
||||
|
||||
|
||||
private:
|
||||
bool isEmergencyExit () { return emergency_; }
|
||||
|
||||
void
|
||||
triggerStartup (Subsys* susy)
|
||||
|
|
@ -161,20 +173,24 @@ namespace lumiera {
|
|||
sigTerm (Subsys* susy, Error* problem) ///< called from subsystem on termination
|
||||
{
|
||||
ASSERT (susy);
|
||||
//Lock guard (this);
|
||||
Lock sync (this);
|
||||
triggerEmergency(problem);
|
||||
ERROR_IF (susy->isRunning(), lumiera, "Subsystem '%s' signals termination, "
|
||||
"without resetting running state", cStr(*susy));
|
||||
removeall (running_, susy);
|
||||
shutdownAll();
|
||||
//guard.notify();
|
||||
sync.notify();
|
||||
}
|
||||
|
||||
bool
|
||||
volatile bool
|
||||
allDead ()
|
||||
{
|
||||
if (isEmergencyExit())
|
||||
; //Lock(this).setTimeout(EMERGENCYTIMEOUT);
|
||||
{
|
||||
Lock sync(this);
|
||||
if (!sync.isTimedWait())
|
||||
sync.setTimeout(EMERGENCYTIMEOUT);
|
||||
}
|
||||
|
||||
return isnil (running_); // end wait if no running subsystem left
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include "lib/allocationcluster.hpp"
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/util.hpp"
|
||||
#include "lib/sync.hpp"
|
||||
|
||||
using util::isnil;
|
||||
|
||||
|
|
@ -38,9 +39,9 @@ namespace lib {
|
|||
* successful ctor call of the object being allocated. Allocations and commits
|
||||
* can be assumed to come in pairs, thus if an allocation immediately follows
|
||||
* another one (without commit), the previous allocation can be considered
|
||||
* a failure and can be dropped silently. After an allocation has succeeds
|
||||
* a failure and can be dropped silently. After an allocation succeeds
|
||||
* (i.e. was committed), the MemoryManager is in charge for the lifecycle
|
||||
* of the object within the allocated spaces and has to guarantee calling
|
||||
* of the object within the allocated space and has to guarantee calling
|
||||
* it's dtor, either on shutdown or on explicit #purge() -- the type info
|
||||
* structure handed in on initialisation provides a means for invoking
|
||||
* the dtor without actually knowing the object's type.
|
||||
|
|
@ -55,6 +56,7 @@ namespace lib {
|
|||
* on real-world measurements, not guessing.
|
||||
*/
|
||||
class AllocationCluster::MemoryManager
|
||||
: public Sync<RecursiveLock_NoWait>
|
||||
{
|
||||
typedef std::vector<char*> MemTable;
|
||||
TypeInfo type_;
|
||||
|
|
@ -81,7 +83,7 @@ namespace lib {
|
|||
void
|
||||
AllocationCluster::MemoryManager::reset (TypeInfo info)
|
||||
{
|
||||
ClassLock<MemoryManager> guard();
|
||||
Lock instance();
|
||||
|
||||
if (0 < mem_.size()) purge();
|
||||
type_ = info;
|
||||
|
|
@ -96,7 +98,7 @@ namespace lib {
|
|||
void
|
||||
AllocationCluster::MemoryManager::purge()
|
||||
{
|
||||
ClassLock<MemoryManager> guard();
|
||||
Lock instance();
|
||||
|
||||
REQUIRE (type_.killIt, "we need a deleter function");
|
||||
REQUIRE (0 < type_.allocSize, "allocation size unknown");
|
||||
|
|
@ -120,7 +122,7 @@ namespace lib {
|
|||
inline void*
|
||||
AllocationCluster::MemoryManager::allocate()
|
||||
{
|
||||
ClassLock<MemoryManager> guard();
|
||||
Lock instance();
|
||||
|
||||
REQUIRE (0 < type_.allocSize);
|
||||
REQUIRE (top_ <= mem_.size());
|
||||
|
|
@ -140,7 +142,7 @@ namespace lib {
|
|||
inline void
|
||||
AllocationCluster::MemoryManager::commit (void* pendingAlloc)
|
||||
{
|
||||
ClassLock<MemoryManager> guard();
|
||||
Lock instance();
|
||||
|
||||
REQUIRE (pendingAlloc);
|
||||
ASSERT (top_ < mem_.size());
|
||||
|
|
@ -174,8 +176,8 @@ namespace lib {
|
|||
AllocationCluster::~AllocationCluster() throw()
|
||||
{
|
||||
try
|
||||
{
|
||||
ClassLock<AllocationCluster> guard();
|
||||
{ // avoiding a per-instance lock for now.
|
||||
ClassLock<AllocationCluster> guard(); // (see note in the class description)
|
||||
|
||||
TRACE (memory, "shutting down AllocationCluster");
|
||||
for (size_t i = typeHandlers_.size(); 0 < i; --i)
|
||||
|
|
@ -213,8 +215,8 @@ namespace lib {
|
|||
{
|
||||
ASSERT (0 < slot);
|
||||
|
||||
{
|
||||
ClassLock<AllocationCluster> guard(); /////TODO: decide tradeoff: lock just the instance, or lock the AllocationCluster class?
|
||||
{ // avoiding a per-instance lock for now.
|
||||
ClassLock<AllocationCluster> guard(); // (see note in the class description)
|
||||
|
||||
if (slot > typeHandlers_.size())
|
||||
typeHandlers_.resize(slot);
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@ namespace lib {
|
|||
* the object families are to be discarded. Currently
|
||||
* they are just purged in reverse order defined by
|
||||
* the first request for allocating a certain type.
|
||||
* @todo should we use an per-instance lock? We can't avoid
|
||||
* the class-wide lock, unless also the type-ID registration
|
||||
* is done on a per-instance base. AllocationCluster is intended
|
||||
* to be used within the builder, which executes in a dedicated
|
||||
* thread. Thus I doubt lock contention could be a problem and
|
||||
* we can avoid using a mutex per instance. Re-evaluate this!
|
||||
*/
|
||||
class AllocationCluster
|
||||
: boost::noncopyable
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ namespace lumiera {
|
|||
|
||||
/**
|
||||
* Policy for handling multithreaded access to the singleton instance
|
||||
* @todo actually implement this policy using the Lumiera databackend.
|
||||
* @todo we could consider using a single shared lock for all singleton types...
|
||||
*/
|
||||
template<class S>
|
||||
struct Multithreaded
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ namespace lumiera {
|
|||
* For each possible call entry point via some subclass of the visitable hierarchy,
|
||||
* we maintain a dispatcher table to keep track of all concrete tool implementations
|
||||
* able to receive and process calls on objects of this subclass.
|
||||
* @param TAR the concrete target (subclass) type within the visitable hierarchy
|
||||
* @param TOOL the overall tool family (base class of all concrete tools)
|
||||
*/
|
||||
template<class TAR, class TOOL>
|
||||
class Dispatcher
|
||||
|
|
@ -140,7 +142,7 @@ namespace lumiera {
|
|||
void
|
||||
accomodate (size_t index)
|
||||
{
|
||||
ClassLock<Dispatcher> guard();
|
||||
ClassLock<Dispatcher> guard(); // note: this lock is also used for the singleton!
|
||||
if (index > table_.size())
|
||||
table_.resize (index); // performance bottleneck?? TODO: measure the real impact!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
** A piece of implementation code factored out into a separate header (include).
|
||||
** Only used in defsmanager.cpp and for the unit tests. We can't place it into
|
||||
** a separate compilation unit, because defsmanager.cpp defines some explicit
|
||||
** template instantiaton, which cause the different Slots of the DefsrRegistry#table_
|
||||
** template instantiation, which cause the different Slots of the DefsrRegistry#table_
|
||||
** to be filled with data and defaults for the specific Types.
|
||||
**
|
||||
** @see mobject::session::DefsManager
|
||||
|
|
@ -80,7 +80,7 @@ namespace mobject {
|
|||
};
|
||||
|
||||
/** we maintain an independent defaults registry
|
||||
* for every participating kind of objects */
|
||||
* for every participating kind of object. */
|
||||
typedef std::vector< P<TableEntry> > Table;
|
||||
|
||||
|
||||
|
|
@ -180,8 +180,8 @@ namespace mobject {
|
|||
|
||||
|
||||
/**
|
||||
* @internal Helper for organizing preconfigured default objects.
|
||||
* Maintaines a collection of objects known or encountered as "default"
|
||||
* @internal Helper for organising preconfigured default objects.
|
||||
* Maintains a collection of objects known or encountered as "default"
|
||||
* for a given type. This collection is ordered by "degree of constriction",
|
||||
* which is implemented by counting the number of predicates in the query
|
||||
* used to define or identify each object.
|
||||
|
|
@ -206,17 +206,17 @@ namespace mobject {
|
|||
II p,i,e;
|
||||
P<TAR> next, ptr;
|
||||
|
||||
Iter (II from, II to) ///< just ennumerates the given range
|
||||
Iter (II from, II to) ///< just enumerates the given range
|
||||
: p(from), i(from), e(to)
|
||||
{
|
||||
if (i!=e) ++i; // p is next to be tested, i always one ahead
|
||||
operator++ ();
|
||||
}
|
||||
|
||||
Iter (II match, II from, II to) ///< returns direct match first, then ennumerates
|
||||
Iter (II match, II from, II to) ///< returns direct match first, then enumerates
|
||||
: p(match), i(from), e(to)
|
||||
{
|
||||
operator++ (); // init to first element (or to null if emty)
|
||||
operator++ (); // init to first element (or to null if empty)
|
||||
}
|
||||
|
||||
P<TAR> findNext () throw()
|
||||
|
|
@ -233,7 +233,7 @@ namespace mobject {
|
|||
|
||||
public:
|
||||
P<TAR> operator* () { return ptr; }
|
||||
bool hasNext () { return next || findNext(); }
|
||||
bool hasNext () { return next || findNext(); }
|
||||
Iter operator++ (int) { Iter tmp=*this; operator++(); return tmp; }
|
||||
Iter& operator++ ()
|
||||
{
|
||||
|
|
@ -243,12 +243,12 @@ namespace mobject {
|
|||
}
|
||||
};
|
||||
|
||||
/** find a sequence of "default" objects possibliy matching the query.
|
||||
/** find a sequence of "default" objects possibly matching the query.
|
||||
* If there was a registration for some object of the given kind with
|
||||
* the \em same query, this one will be first in the sequence. Besides,
|
||||
* the sequence will yield all still existing registered "default" objects
|
||||
* of this kind, ordered ascending by "degree of constriction", i.e. starting
|
||||
* with the object registered togehter with the shortest query.
|
||||
* with the object registered together with the shortest query.
|
||||
* @return a forward input iterator yielding this sequence
|
||||
* @note none of the queries will be evaluated (we're just counting predicates)
|
||||
*/
|
||||
|
|
@ -265,7 +265,7 @@ namespace mobject {
|
|||
typename Registry::iterator end = registry.end();
|
||||
|
||||
if (pos==end)
|
||||
return Iter<TAR> (registry.begin(), end); // just ennumerate contents
|
||||
return Iter<TAR> (registry.begin(), end); // just enumerate contents
|
||||
else
|
||||
return Iter<TAR> (pos, registry.begin(), end); // start with direct match
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue