reconsider synchronisation and switch some parts to instance based locks.

This commit is contained in:
Fischlurch 2008-12-28 05:20:35 +01:00
parent e801c324bf
commit 75bdc877dc
9 changed files with 70 additions and 49 deletions

View file

@ -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. "

View file

@ -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();
}

View file

@ -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

View file

@ -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
}

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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!
}

View file

@ -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
}