diff --git a/SConstruct b/SConstruct index 52493aa4b..e38511152 100644 --- a/SConstruct +++ b/SConstruct @@ -255,6 +255,9 @@ def configurePlatform(env): if not conf.CheckPkgConfig('glibmm-2.4', '2.16'): problems.append('Unable to configure Lib glib--, exiting.') + if not conf.CheckPkgConfig('gthread-2.0', '2.16'): + problems.append('Need gthread support lib for glib-- based thread handling.') + if not conf.CheckPkgConfig('cairomm-1.0', 0.6): problems.append('Unable to configure Cairo--, exiting.') @@ -312,6 +315,8 @@ def defineBuildTargets(env, artifacts): setup sub-environments with special build options if necessary. We use a custom function to declare a whole tree of srcfiles. """ + env.mergeConf(['glibmm-2.4','gthread-2.0']) + # use PCH to speed up building # env['GCH'] = ( env.PrecompiledHeader('$SRCDIR/pre.hpp') # + env.PrecompiledHeader('$SRCDIR/pre_a.hpp') diff --git a/src/common/subsys.cpp b/src/common/subsys.cpp index 41fd63469..2becff32b 100644 --- a/src/common/subsys.cpp +++ b/src/common/subsys.cpp @@ -53,7 +53,7 @@ namespace lumiera { bool Subsys::isRunning() { - //Lock guard (*this); + //Lock guard (this); return checkRunningState(); } diff --git a/src/common/subsystemrunner.hpp b/src/common/subsystemrunner.hpp index 4b398606b..363b4c8cc 100644 --- a/src/common/subsystemrunner.hpp +++ b/src/common/subsystemrunner.hpp @@ -27,7 +27,7 @@ #include "lib/error.hpp" #include "lib/util.hpp" #include "common/subsys.hpp" -#include "lib/multithread.hpp" +#include "lib/sync.hpp" #include #include @@ -87,6 +87,7 @@ namespace lumiera { * @see main.cpp */ class SubsystemRunner +// : Sync { Option& opts_; volatile bool emergency_; @@ -108,7 +109,7 @@ namespace lumiera { void maybeRun (Subsys& susy) { - //Lock guard (*this); + //Lock guard (this); if (!susy.isRunning() && susy.shouldStart (opts_)) triggerStartup (&susy); @@ -119,14 +120,14 @@ namespace lumiera { void shutdownAll () { - //Lock guard (*this); + //Lock guard (this); for_each (running_, killIt_); } bool wait () { - //Lock(*this).wait (&SubsystemRunner::allDead); + //Lock(this).wait (&SubsystemRunner::allDead); return isEmergencyExit(); } @@ -160,7 +161,7 @@ namespace lumiera { sigTerm (Subsys* susy, Error* problem) ///< called from subsystem on termination { ASSERT (susy); - //Lock guard (*this); + //Lock guard (this); triggerEmergency(problem); ERROR_IF (susy->isRunning(), lumiera, "Subsystem '%s' signals termination, " "without resetting running state", cStr(*susy)); @@ -173,7 +174,7 @@ namespace lumiera { allDead () { if (isEmergencyExit()) - ; //Lock(*this).setTimeout(EMERGENCYTIMEOUT); + ; //Lock(this).setTimeout(EMERGENCYTIMEOUT); return isnil (running_); // end wait if no running subsystem left } diff --git a/src/include/nobugcfg.h b/src/include/nobugcfg.h index 3653b9874..37b336503 100644 --- a/src/include/nobugcfg.h +++ b/src/include/nobugcfg.h @@ -88,6 +88,7 @@ namespace lumiera { NOBUG_DECLARE_FLAG (render); ///< logging channel focusing on the render engine's workings NOBUG_DECLARE_FLAG (config); ///< logging channel covering application and session configuration NOBUG_DECLARE_FLAG (memory); ///< logging channel covering memory management issues + NOBUG_DECLARE_FLAG (sync); ///< especially for tracing synchronisation NOBUG_DECLARE_FLAG (test); @@ -109,6 +110,7 @@ namespace lumiera { NOBUG_CPP_DEFINE_FLAG_PARENT (operate, lumiera); NOBUG_CPP_DEFINE_FLAG_PARENT_LIMIT (render, lumiera, LOG_WARNING); NOBUG_CPP_DEFINE_FLAG_PARENT_LIMIT (memory, lumiera, LOG_WARNING); + NOBUG_CPP_DEFINE_FLAG_PARENT_LIMIT (sync, memory, LOG_WARNING); NOBUG_CPP_DEFINE_FLAG_PARENT_LIMIT (test, all, LOG_ERR); diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 2de1050c4..4806841a4 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -71,7 +71,7 @@ noinst_HEADERS += \ $(liblumiera_la_srcdir)/visitor.hpp \ $(liblumiera_la_srcdir)/visitordispatcher.hpp \ $(liblumiera_la_srcdir)/visitorpolicies.hpp \ - $(liblumiera_la_srcdir)/multithread.hpp \ + $(liblumiera_la_srcdir)/sync.hpp \ $(liblumiera_la_srcdir)/p.hpp \ $(liblumiera_la_srcdir)/query.hpp \ $(liblumiera_la_srcdir)/singletonfactory.hpp \ diff --git a/src/lib/allocationcluster.cpp b/src/lib/allocationcluster.cpp index 1d6da2dbb..f60fd56a8 100644 --- a/src/lib/allocationcluster.cpp +++ b/src/lib/allocationcluster.cpp @@ -81,7 +81,7 @@ namespace lib { void AllocationCluster::MemoryManager::reset (TypeInfo info) { - Thread::Lock guard SIDEEFFECT; + ClassLock guard(); if (0 < mem_.size()) purge(); type_ = info; @@ -96,7 +96,7 @@ namespace lib { void AllocationCluster::MemoryManager::purge() { - Thread::Lock guard SIDEEFFECT; + ClassLock guard(); REQUIRE (type_.killIt, "we need a deleter function"); REQUIRE (0 < type_.allocSize, "allocation size unknown"); @@ -120,7 +120,7 @@ namespace lib { inline void* AllocationCluster::MemoryManager::allocate() { - Thread::Lock guard SIDEEFFECT; + ClassLock guard(); REQUIRE (0 < type_.allocSize); REQUIRE (top_ <= mem_.size()); @@ -140,7 +140,7 @@ namespace lib { inline void AllocationCluster::MemoryManager::commit (void* pendingAlloc) { - Thread::Lock guard SIDEEFFECT; + ClassLock guard(); REQUIRE (pendingAlloc); ASSERT (top_ < mem_.size()); @@ -175,7 +175,7 @@ namespace lib { { try { - Thread::Lock guard SIDEEFFECT + ClassLock guard(); TRACE (memory, "shutting down AllocationCluster"); for (size_t i = typeHandlers_.size(); 0 < i; --i) @@ -214,7 +214,7 @@ namespace lib { ASSERT (0 < slot); { - Thread::Lock guard SIDEEFFECT; /////TODO: decide tradeoff: lock just the instance, or lock the AllocationCluster class? + ClassLock guard(); /////TODO: decide tradeoff: lock just the instance, or lock the AllocationCluster class? if (slot > typeHandlers_.size()) typeHandlers_.resize(slot); diff --git a/src/lib/allocationcluster.hpp b/src/lib/allocationcluster.hpp index ee9c61933..8b639199c 100644 --- a/src/lib/allocationcluster.hpp +++ b/src/lib/allocationcluster.hpp @@ -51,17 +51,17 @@ #include #include -#include "lib/multithread.hpp" #include "lib/error.hpp" +#include "lib/sync-classlock.hpp" #include "lib/scopedholder.hpp" #include "lib/scopedholdertransfer.hpp" namespace lib { + using boost::scoped_ptr; - using lumiera::Thread; - + /** * A pile of objects sharing common allocation and lifecycle. * AllocationCluster owns a number of object families of various types. @@ -223,7 +223,7 @@ namespace lib { static TypeInfo setup() { - Thread::Lock guard SIDEEFFECT; + ClassLock guard(); if (!id_) id_= ++maxTypeIDs; diff --git a/src/lib/error.hpp b/src/lib/error.hpp index bce21423c..50d43187d 100644 --- a/src/lib/error.hpp +++ b/src/lib/error.hpp @@ -40,7 +40,7 @@ namespace lumiera { LUMIERA_ERROR_DECLARE(EXCEPTION); /** - * Interface and Baseclass of all Exceptions thrown + * Interface and Base class of all Exceptions thrown * from within Lumiera (C++) code. Common operations * for getting an diagnostic message and for obtaining * the root cause, i.e. the first exception encountered @@ -56,7 +56,7 @@ namespace lumiera { Error (const Error&) throw(); virtual ~Error () throw() {}; - /** yield a diagnostic message characterizing the problem */ + /** yield a diagnostic message characterising the problem */ virtual const char* what () const throw(); /** the internal Lumiera-error-ID (was set as C-errorstate in ctor) */ @@ -68,12 +68,12 @@ namespace lumiera { /** If this exception was caused by a chain of further exceptions, * return the description of the first one registered in this throw sequence. * This works only if every exceptions thrown as a consequence of another exception - * is propperly constructed by passing the original exception to the constructor + * is properly constructed by passing the original exception to the constructor * @return the description string, maybe empty (if there is no known root cause) */ const string& rootCause () const throw() { return this->cause_; } - /** replace the previous or default friendly message for the user. To be localized. */ + /** replace the previous or default friendly message for the user. To be localised. */ Error& setUsermsg (const string& newMsg) throw() { this->msg_ = newMsg; return *this; } /** give additional developer info. Typically used at intermediate handlers to add context. */ @@ -82,10 +82,10 @@ namespace lumiera { private: const char* id_; ///< an LUMIERA_ERROR id, which is set as errorstate on construction - string msg_; ///< friendly message intended for users (to be localized) + string msg_; ///< friendly message intended for users (to be localised) string desc_; ///< detailed description of the error situation for the developers mutable string what_; ///< buffer for generating the detailed description on demand - const string cause_; ///< descriptoin of first exception encountered in the chain + const string cause_; ///< description of first exception encountered in the chain static const string extractCauseMsg (const std::exception&) throw(); }; @@ -95,7 +95,7 @@ namespace lumiera { - /* === Exception Subcategories === */ + /* === Exception Sub-categories === */ namespace error { @@ -123,10 +123,10 @@ namespace lumiera { /** Macro for creating derived exception classes properly * integrated into Lumiera's exception hierarchy. Using - * this macro asures that the new class will get the full + * this macro assures that the new class will get the full * set of constructors and behaviour common to all exception * classes, so it should be used when creating an derived - * exception type for more then stricly local purposes + * exception type for more then strictly local purposes */ #define LUMIERA_EXCEPTION_DECLARE(CLASS, PARENT, _ID_) \ class CLASS : public PARENT \ diff --git a/src/lib/multithread.hpp b/src/lib/multithread.hpp deleted file mode 100644 index 2d3b4b31f..000000000 --- a/src/lib/multithread.hpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - MULTITHREAD.hpp - generic interface for multithreading primitives - - Copyright (C) Lumiera.org - 2008, Christian Thaeter - Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. - - 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. - - 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 02139, USA. - -*/ - - - -#ifndef LUMIERA_MULTITHREAD_H -#define LUMIERA_MULTITHREAD_H - -#include "include/nobugcfg.h" -#include "lib/util.hpp" - - -namespace lumiera - { - - /** - * Interface/Policy for managing parallelism issues. - * Basically everything is forwarded to the corresponding backend functions, - * because managing threads and locking belongs to the Lumiera backend layer. - * - * @todo actually implement this policy using the Lumiera databackend. - */ - struct Thread - { - template - class Lock - { - public: - Lock() { TODO ("aquire Thread Lock for Class"); } - Lock(X*) { TODO ("aquire Thread Lock for Instance"); } - ~Lock() { TODO ("release Thread Lock"); } - }; - }; - - -} // namespace lumiera -#endif diff --git a/src/lib/mutex.h b/src/lib/mutex.h index 86fb48944..0111c9fff 100644 --- a/src/lib/mutex.h +++ b/src/lib/mutex.h @@ -101,26 +101,26 @@ LUMIERA_ERROR_DECLARE (MUTEX_DESTROY); /** * Recursive Mutual exclusive section. */ -#define LUMIERA_RECMUTEX_SECTION(nobugflag, mtx) \ - for (lumiera_mutexacquirer NOBUG_CLEANUP(lumiera_mutexacquirer_ensureunlocked) lumiera_mutex_section_ = {(LumieraMutex)1 \ - NOBUG_RESOURCE_HANDLE_COMMA_INITIALIZER}; \ - lumiera_mutex_section_.mutex;) \ - for ( \ - ({ \ - lumiera_mutex_section_.mutex = (mtx); \ - NOBUG_RESOURCE_HANDLE_INIT (lumiera_mutex_section_.rh); \ - RESOURCE_ENTER (nobugflag, (mtx)->rh, "acquire recmutex", &lumiera_mutex_section_, \ - NOBUG_RESOURCE_RECURSIVE, lumiera_mutex_section_.rh); \ - if (pthread_mutex_lock (&(mtx)->mutex)) LUMIERA_DIE (MUTEX_LOCK); \ - }); \ - lumiera_mutex_section_.mutex; \ - ({ \ - if (lumiera_mutex_section_.mutex) \ - { \ - pthread_mutex_unlock (&lumiera_mutex_section_.mutex->mutex); \ - lumiera_mutex_section_.mutex = NULL; \ - RESOURCE_LEAVE(nobugflag, lumiera_mutex_section_.rh); \ - } \ +#define LUMIERA_RECMUTEX_SECTION(nobugflag, mtx) \ + for (lumiera_mutexacquirer NOBUG_CLEANUP(lumiera_mutexacquirer_ensureunlocked) lumiera_mutex_section_ \ + = {(LumieraMutex)1 NOBUG_RESOURCE_HANDLE_COMMA_INITIALIZER}; \ + lumiera_mutex_section_.mutex;) \ + for ( \ + ({ \ + lumiera_mutex_section_.mutex = (mtx); \ + NOBUG_RESOURCE_HANDLE_INIT (lumiera_mutex_section_.rh); \ + RESOURCE_ENTER (nobugflag, (mtx)->rh, "acquire recmutex", &lumiera_mutex_section_, \ + NOBUG_RESOURCE_RECURSIVE, lumiera_mutex_section_.rh); \ + if (pthread_mutex_lock (&(mtx)->mutex)) LUMIERA_DIE (MUTEX_LOCK); \ + }); \ + lumiera_mutex_section_.mutex; \ + ({ \ + if (lumiera_mutex_section_.mutex) \ + { \ + pthread_mutex_unlock (&lumiera_mutex_section_.mutex->mutex); \ + lumiera_mutex_section_.mutex = NULL; \ + RESOURCE_LEAVE(nobugflag, lumiera_mutex_section_.rh); \ + } \ })) diff --git a/src/lib/scopedholder.hpp b/src/lib/scopedholder.hpp index c70f9f5e4..6dbb8c37a 100644 --- a/src/lib/scopedholder.hpp +++ b/src/lib/scopedholder.hpp @@ -221,7 +221,7 @@ namespace lib { return created_? &_ThisType::created_ : 0; } - bool operator! () const { return !created_; } + bool operator! () const { return !created_; } friend void diff --git a/src/lib/singletonpolicies.hpp b/src/lib/singletonpolicies.hpp index 2d117e2cd..ba63d4a99 100644 --- a/src/lib/singletonpolicies.hpp +++ b/src/lib/singletonpolicies.hpp @@ -34,16 +34,16 @@ This code is heavily inspired by #ifndef LUMIERA_SINGLETONPOLICIES_H #define LUMIERA_SINGLETONPOLICIES_H -#include "lib/multithread.hpp" #include "lib/error.hpp" +#include "lib/sync-classlock.hpp" #include -namespace lumiera - { - namespace singleton - { +namespace lumiera { + namespace singleton { + + /* === several Policies usable in conjunction with lumiera::Singleton === */ /** @@ -77,11 +77,11 @@ namespace lumiera static S* create () { return new S; } static void destroy (S* pS) { delete pS; } }; - - - - + + + + typedef void (*DelFunc)(void); using std::vector; @@ -139,7 +139,7 @@ namespace lumiera struct Multithreaded { typedef volatile S VolatileType; - typedef lumiera::Thread::Lock Lock; + typedef lib::ClassLock Lock; }; @@ -152,7 +152,7 @@ namespace lumiera typedef S VolatileType; struct Lock {}; }; - + } // namespace singleton diff --git a/src/lib/sync-classlock.hpp b/src/lib/sync-classlock.hpp new file mode 100644 index 000000000..2cb37d17a --- /dev/null +++ b/src/lib/sync-classlock.hpp @@ -0,0 +1,115 @@ +/* + SYNC-CLASSLOCK.hpp - special case of object based locking tied directly to a type + + Copyright (C) Lumiera.org + 2008, Christian Thaeter + Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + 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. + + 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 02139, USA. + +*/ + +/** @file sync-classlock.hpp + ** A special implementation of lib::Sync, where the storage of the object monitor + ** is associated directly to a type rather then to a single object instance. While + ** being problematic in conjunction with static startup/shutdown, doing so is sometimes + ** necessary to setup type based dispatcher tables, managing singleton creation etc. + ** + ** @note simply using the ClassLock may cause a Monitor object (with a mutex) to be + ** created at static initialisation and destroyed on application shutdown. + ** @see singletonfactory.hpp usage example + */ + + +#ifndef LIB_SYNC_CLASSLOCK_H +#define LIB_SYNC_CLASSLOCK_H + +#include "lib/sync.hpp" + + +namespace lib { + + namespace { // implementation details + + template + struct NiftyHolder + { + static uint accessed_; + static char content_[sizeof(X)]; + + NiftyHolder() + { + if (!accessed_) + new(content_) X(); + ++accessed_; + } + + ~NiftyHolder() + { + --accessed_; + if (0==accessed_) + get().~X(); + } + + X& + get() + { + X* obj = reinterpret_cast (&content_); + ASSERT (obj, "Logic of Schwartz counter broken."); + return *obj; + } + }; + + template + uint NiftyHolder::accessed_; + + template + char NiftyHolder::content_[sizeof(X)]; + + } // (End) implementation details + + + + /** + * A synchronisation protection guard employing a lock scoped + * to the parameter type as a whole, not an individual instance. + * After creating an instance, every other access specifying the same + * type is blocked. + * @warn beware of recursion when using a nonrecursive Mutex + * @see Sync::Lock the usual simple instance-bound variant + */ + template + class ClassLock + : public Sync::Lock + { + typedef typename Sync::Lock Lock; + typedef typename sync::Monitor Monitor; + + Monitor& + getPerClassMonitor() + { + static NiftyHolder __used_here; + return __used_here.get(); + } + + public: + ClassLock() : Lock (getPerClassMonitor()) {} + + uint use_count() { return NiftyHolder::accessed_; } + }; + + +} // namespace lib +#endif diff --git a/src/lib/sync.hpp b/src/lib/sync.hpp new file mode 100644 index 000000000..81b2ec019 --- /dev/null +++ b/src/lib/sync.hpp @@ -0,0 +1,327 @@ +/* + SYNC.hpp - generic helper for object based locking and synchronisation + + Copyright (C) Lumiera.org + 2008, Christian Thaeter + Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + 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. + + 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 02139, USA. + +*/ + +/** @file sync.hpp + ** Collection of helpers and wrappers to support dealing with concurrency issues. + ** Actually, everything is implemented either by the Lumiera backend, or directly + ** by pthread. The purpose is to support and automate the most common use cases + ** in object oriented style. + ** + ** @see mutex.h + ** @see sync-locking-test.cpp + ** @see sync-waiting-test.cpp + ** @see asset::AssetManager::reg() usage example + ** @see subsystemrunner.hpp usage example + */ + + +#ifndef LIB_SYNC_H +#define LIB_SYNC_H + +#include "include/nobugcfg.h" +#include "lib/util.hpp" +#include "include/error.hpp" + +extern "C" { +#include "lib/mutex.h" +#include "lib/condition.h" +} + +#include +#include +#include + + +namespace lib { + + using boost::scoped_ptr; + + + /** Helpers and building blocks for Monitor based synchronisation */ + namespace sync { + + + struct Wrapped_LumieraExcMutex + : public lumiera_mutex + { + protected: + Wrapped_LumieraExcMutex() { lumiera_mutex_init (this, "Obj.Monitor ExclMutex", &NOBUG_FLAG(sync)); } + ~Wrapped_LumieraExcMutex() { lumiera_mutex_destroy (this, &NOBUG_FLAG(sync)); } + + //------------------Resource-Tracking------ + void __may_block() { TODO ("Record we may block on mutex"); } + void __enter() { TODO ("Record we successfully acquired the mutex"); } + void __leave() { TODO ("Record we are releasing the mutex"); } + }; + + + struct Wrapped_LumieraRecMutex + : public lumiera_mutex + { + protected: + Wrapped_LumieraRecMutex() { lumiera_recmutex_init (this, "Obj.Monitor ExclMutex", &NOBUG_FLAG(sync)); } + ~Wrapped_LumieraRecMutex() { lumiera_mutex_destroy (this, &NOBUG_FLAG(sync)); } + + //------------------Resource-Tracking------ + void __may_block() { TODO ("Record we may block on mutex"); } + void __enter() { TODO ("Record we successfully acquired the mutex"); } + void __leave() { TODO ("Record we are releasing the mutex"); } + }; + + + struct Wrapped_LumieraExcCond + : public lumiera_condition + { + protected: + Wrapped_LumieraExcCond() { lumiera_condition_init (this, "Obj.Monitor Condition", &NOBUG_FLAG(sync) ); } + ~Wrapped_LumieraExcCond() { lumiera_condition_destroy (this, &NOBUG_FLAG(sync) ); } + + //------------------Resource-Tracking------ + void __may_block() { TODO ("Record we may block on mutex"); } + void __enter() { TODO ("Record we successfully acquired the mutex"); } + void __leave() { TODO ("Record we are releasing the mutex"); } + }; + + + struct Wrapped_LumieraRecCond + : public lumiera_condition + { + protected: + Wrapped_LumieraRecCond() { lumiera_condition_init (this, "Obj.Monitor Condition", &NOBUG_FLAG(sync) ); } ////////TODO + ~Wrapped_LumieraRecCond() { lumiera_condition_destroy (this, &NOBUG_FLAG(sync) ); } + + //------------------Resource-Tracking------ + void __may_block() { TODO ("Record we may block on mutex"); } + void __enter() { TODO ("Record we successfully acquired the mutex"); } + void __leave() { TODO ("Record we are releasing the mutex"); } + }; + + + template + class Mutex + : protected MTX + { + protected: + using MTX::mutex; + using MTX::__may_block; + using MTX::__enter; + using MTX::__leave; + + public: + void + acquire() + { + __may_block(); + + if (pthread_mutex_lock (&mutex)) + throw lumiera::error::State("Mutex acquire failed."); ///////TODO capture the error-code + + __enter(); + } + + void + release() + { + __leave(); + + pthread_mutex_unlock (&mutex); + } + + }; + + + class Timeout; + + + template + class Condition + : public Mutex + { + protected: + using CDX::cond; + using CDX::mutex; + + public: + void + signal (bool wakeAll=false) + { + if (wakeAll) + pthread_cond_broadcast (&cond); + else + pthread_cond_signal (&cond); + } + + + template + bool + wait (BF& predicate, Timeout& waitEndTime) + { + int err=0; + while (!predicate() && !err) + if (waitEndTime) + err = pthread_cond_timedwait (&cond, &mutex, &waitEndTime); + else + err = pthread_cond_wait (&cond, &mutex); + + if (!err) return true; + if (ETIMEDOUT==err) return false; + + throw lumiera::error::State ("Condition wait failed."); ///////////TODO extract error-code + } + }; + + /** helper for specifying an optional timeout + * for an timed wait. It wraps a timespec-struct + * and allows for easy initialisation by a given + * relative offset. + */ + struct Timeout + : timespec + { + Timeout() { tv_sec=tv_nsec=0; } + + /** initialise to NOW() + offset (in milliseconds) */ + Timeout& + setOffset (ulong offs) + { + if (offs) + { + clock_gettime(CLOCK_REALTIME, this); + tv_sec += offs / 1000; + tv_nsec += 1000000 * (offs % 1000); + if (tv_nsec > 1000000000) + { + tv_sec += tv_nsec / 1000000000; + tv_nsec %= 1000000000; + } } + return *this; + } + + operator bool() { return 0 != tv_sec; } // allows if (timeout_).... + }; + + typedef volatile bool& Flag; + + struct BoolFlagPredicate + { + Flag flag_; + BoolFlagPredicate (Flag f) : flag_(f) {} + + bool operator() () { return flag_; } + }; + + + template + class Monitor + : IMPL + { + Timeout timeout_; + + public: + Monitor() {} + ~Monitor() {} + + void acquireLock() { IMPL::acquire(); } + void releaseLock() { IMPL::release(); } + + void signal(bool a){ IMPL::signal(a); } + + bool wait (Flag flag, ulong timedwait=0) + { + BoolFlagPredicate checkFlag(flag); + return IMPL::wait(checkFlag, timeout_.setOffset(timedwait)); + } + + void setTimeout(ulong relative) {timeout_.setOffset(relative);} + bool isTimedWait() {return (timeout_);} + }; + + typedef Mutex NonrecursiveLock_NoWait; + typedef Mutex RecursiveLock_NoWait; + typedef Condition NonrecursiveLock_Waitable; + typedef Condition RecursiveLock_Waitable; + + + } // namespace sync (helpers and building blocks) + + + + using sync::NonrecursiveLock_NoWait; + using sync::NonrecursiveLock_Waitable; + using sync::RecursiveLock_NoWait; + using sync::RecursiveLock_Waitable; + + + /** + * Facility for monitor object based locking. + * To be attached either on a per class base or per object base. + * Typically, the client class will inherit from this template (but it + * is possible to use it stand-alone, if inheriting isn't an option). + * The interface for clients to access the functionality is the embedded + * Lock template, which should be instantiated as an automatic variable + * within the scope to be protected. + * + * @todo actually implement this facility using the Lumiera backend. + */ + template + class Sync + { + typedef sync::Monitor Monitor; + Monitor objectMonitor_; + + static Monitor& + getMonitor(Sync* forThis) + { + REQUIRE (forThis); + return forThis->objectMonitor_; + } + + + public: + class Lock + { + Monitor& mon_; + + public: + template + Lock(X* it) : mon_(getMonitor(it)){ mon_.acquireLock(); } + Lock(Monitor& m) : mon_(m) { mon_.acquireLock(); } + ~Lock() { mon_.releaseLock(); } + + template + bool wait (C& cond, ulong time=0) { return mon_.wait(cond,time);} + void setTimeout(ulong time) { mon_.setTimeout(time); } + + void notifyAll() { mon_.signal(true); } + void notify() { mon_.signal(false);} + + }; + + + }; + + + + +} // namespace lumiera +#endif diff --git a/src/lib/visitordispatcher.hpp b/src/lib/visitordispatcher.hpp index c42d1b2b2..37bee9007 100644 --- a/src/lib/visitordispatcher.hpp +++ b/src/lib/visitordispatcher.hpp @@ -27,17 +27,19 @@ #include "lib/error.hpp" #include "lib/util.hpp" +#include "lib/sync-classlock.hpp" #include "lib/singleton.hpp" -#include "lib/multithread.hpp" +#include "lib/util.hpp" #include -namespace lumiera - { - namespace visitor - { - +namespace lumiera { + namespace visitor { + + using lib::ClassLock; + + template class Tag; @@ -62,7 +64,7 @@ namespace lumiera static void generateID (size_t& id) { - Thread::Lock guard SIDEEFFECT; + ClassLock guard(); if (!id) id = ++lastRegisteredID; } @@ -88,10 +90,10 @@ namespace lumiera /** storage for the Tag registry for each concrete tool */ template Tag TagTypeRegistry::tag; - + template size_t Tag::lastRegisteredID (0); - + @@ -129,7 +131,7 @@ namespace lumiera } typedef ReturnType (*Trampoline) (TAR&, TOOL& ); - + /** VTable for storing the Trampoline pointers */ std::vector table_; @@ -138,7 +140,7 @@ namespace lumiera void accomodate (size_t index) { - Thread::Lock guard SIDEEFFECT; + ClassLock guard(); if (index > table_.size()) table_.resize (index); // performance bottleneck?? TODO: measure the real impact! } @@ -173,7 +175,7 @@ namespace lumiera { return tool.onUnknown (target); } - + public: static Singleton > instance; @@ -201,11 +203,11 @@ namespace lumiera } }; - + /** storage for the dispatcher table(s) */ template Singleton > Dispatcher::instance; - + diff --git a/src/proc/asset/db.hpp b/src/proc/asset/db.hpp index 3a5ddb6d2..a90b7d152 100644 --- a/src/proc/asset/db.hpp +++ b/src/proc/asset/db.hpp @@ -27,6 +27,7 @@ #include "pre_a.hpp" +#include "lib/sync.hpp" #include "proc/asset.hpp" #include "lib/error.hpp" @@ -35,11 +36,14 @@ #include -namespace asset - { +namespace asset { + using std::tr1::static_pointer_cast; using std::tr1::dynamic_pointer_cast; + using lib::Sync; + using lib::RecursiveLock_NoWait; + /* ===== hash implementations ===== */ @@ -83,7 +87,9 @@ namespace asset * instances known to the Asset Manager subsystem. * As of 8/2007 implemented by a hashtable. */ - class DB : private boost::noncopyable + class DB + : private boost::noncopyable, + public Sync { IdHashtable table; diff --git a/src/proc/assetmanager.cpp b/src/proc/assetmanager.cpp index 3c17421a9..0aaa26c0e 100644 --- a/src/proc/assetmanager.cpp +++ b/src/proc/assetmanager.cpp @@ -24,7 +24,7 @@ #include "proc/assetmanager.hpp" #include "proc/asset/db.hpp" -#include "lib/multithread.hpp" +#include "lib/sync.hpp" #include "lib/util.hpp" #include @@ -39,11 +39,10 @@ using boost::bind; using util::for_each; using lumiera::Singleton; -using lumiera::Thread; +using lib::Sync; -namespace asset - { +namespace asset { /** * AssetManager error responses, caused by querying @@ -113,14 +112,15 @@ namespace asset throw(lumiera::error::Invalid) { AssetManager& _aMang (AssetManager::instance()); + DB& registry (_aMang.registry); TODO ("check validity of Ident Category"); ID asset_id (getID (idi)); - Thread::Lock guard SIDEEFFECT; + DB::Lock guard(®istry); TODO ("handle duplicate Registrations"); P smart_ptr (obj, &destroy); - _aMang.registry.put (asset_id, smart_ptr); + registry.put (asset_id, smart_ptr); return asset_id; } @@ -143,11 +143,11 @@ namespace asset throw UnknownID (id); } - /** convienience shortcut for fetching the registered smart-ptr + /** Convenience shortcut for fetching the registered smart-ptr * which is in charge of the given asset instance. By querying * directly asset.id (of type ID), the call to registry.get() * can bypass the dynamic cast, because the type of the asset - * is explicitely given by type KIND. + * is explicitly given by type KIND. */ template P @@ -197,7 +197,7 @@ namespace asset /** * remove the given asset from the internal DB - * together with all its dependants + * together with all its dependents */ void AssetManager::remove (IDA id) diff --git a/src/proc/assetmanager.hpp b/src/proc/assetmanager.hpp index 8f0ca800c..843548247 100644 --- a/src/proc/assetmanager.hpp +++ b/src/proc/assetmanager.hpp @@ -53,8 +53,7 @@ using std::list; -namespace asset - { +namespace asset { class DB; @@ -62,7 +61,8 @@ namespace asset /** * Facade for the Asset subsystem */ - class AssetManager : private boost::noncopyable + class AssetManager + : private boost::noncopyable { asset::DB & registry; diff --git a/src/proc/mobject/session/defsregistry.hpp b/src/proc/mobject/session/defsregistry.hpp index da79f40fa..06133b292 100644 --- a/src/proc/mobject/session/defsregistry.hpp +++ b/src/proc/mobject/session/defsregistry.hpp @@ -40,7 +40,7 @@ #define MOBJECT_SESSION_DEFSREGISTRY_H -#include "lib/multithread.hpp" +#include "lib/sync-classlock.hpp" #include "lib/query.hpp" #include "lib/util.hpp" #include "lib/p.hpp" @@ -53,13 +53,12 @@ #include -namespace mobject - { - namespace session - { +namespace mobject { + namespace session { + using lumiera::P; using lumiera::Query; - using lumiera::Thread; + using lib::ClassLock; using std::tr1::weak_ptr; using std::string; @@ -163,7 +162,7 @@ namespace mobject static void createSlot (Table& table) { - Thread::Lock guard SIDEEFFECT; + ClassLock guard(); if (!index) index = ++maxSlots; if (index > table.size()) diff --git a/tests/40components.tests b/tests/40components.tests index 4ff311a4d..ca079d7ef 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -317,6 +317,21 @@ PLANNED "StreamTypeLifecycle_test" StreamTypeLifecycle_test < Testgroup=ALL diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am index b1366bf3f..19a13ce2f 100644 --- a/tests/lib/Makefile.am +++ b/tests/lib/Makefile.am @@ -58,6 +58,7 @@ test_lib_SOURCES = \ $(testlib_srcdir)/singletontestmocktest.cpp \ $(testlib_srcdir)/streamtypebasicstest.cpp \ $(testlib_srcdir)/streamtypelifecycletest.cpp \ + $(testlib_srcdir)/sync-locking-test.cpp \ $(testlib_srcdir)/test/cmdlinewrappertest.cpp \ $(testlib_srcdir)/test/testoptiontest.cpp \ $(testlib_srcdir)/vectortransfertest.cpp \ diff --git a/tests/lib/meta/generatortest.cpp b/tests/lib/meta/generatortest.cpp index 932b79fe0..c4224b4fa 100644 --- a/tests/lib/meta/generatortest.cpp +++ b/tests/lib/meta/generatortest.cpp @@ -120,7 +120,7 @@ namespace lumiera */ class TypeListGenerator_test : public Test { - virtual void run(Arg arg) + virtual void run(Arg) { NumberBabbler me_can_has_more_numberz; diff --git a/tests/lib/sync-classlock-test.cpp b/tests/lib/sync-classlock-test.cpp new file mode 100644 index 000000000..d53c031ef --- /dev/null +++ b/tests/lib/sync-classlock-test.cpp @@ -0,0 +1,98 @@ +/* + SyncClasslock(Test) - validate the type-based Monitor locking + + Copyright (C) Lumiera.org + 2008, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + 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. + + 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 02139, USA. + +* *****************************************************/ + + +#include "lib/test/run.hpp" +#include "include/error.hpp" + +#include "lib/sync-classlock.hpp" + +//#include + +//using std::cout; +using test::Test; + + +namespace lib { + namespace test { + + namespace { // private test classes and data... + + const uint NUM_INSTANCES = 20; ///< number of probe instances to create + + + /** + * instances of this probe class will be created statically. + * They utilise the class-based locking within ctor and dtor + */ + struct Probe + { + Probe() { ClassLock ctor_lock; } + ~Probe() { ClassLock dtor_lock; } + }; + + + } // (End) test classes and data.... + + + + + + + + + + + /************************************************************************** + * @test check proper handling of class (not instance)-based Monitor locks. + * Because no instance is available in this case, a hidden storage for the + * Monitor object needs to be provided in a way safe for use even in the + * static startup/shutdown phase. This test validates the associated + * refcounting and object creation works as expected. It does \em not + * validate the locking functionality as such. + * + * @see sync.hpp + */ + class SyncClasslock_test : public Test + { + + virtual void + run (Arg) + { + static Probe objs[NUM_INSTANCES]; + + ClassLock get_class_lock; + ASSERT ( 1 == get_class_lock.use_count()); // ClassLock got created exactly once + } + + }; + + + + /** Register this test class... */ + LAUNCHER (SyncClasslock_test, "unit common"); + + + + } // namespace test + +} // namespace lumiera diff --git a/tests/lib/sync-locking-test.cpp b/tests/lib/sync-locking-test.cpp new file mode 100644 index 000000000..e246a8083 --- /dev/null +++ b/tests/lib/sync-locking-test.cpp @@ -0,0 +1,207 @@ +/* + SyncLocking(Test) - check the monitor object based locking + + Copyright (C) Lumiera.org + 2008, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + 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. + + 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 02139, USA. + +* *****************************************************/ + + +#include "lib/test/run.hpp" +#include "include/error.hpp" + +#include "lib/sync.hpp" + +#include + +#include + +using std::cout; +using test::Test; + + +namespace lib { + namespace test { + + namespace { // private test classes and data... + + const uint NUM_COUNTERS = 20; ///< number of independent counters to increment in parallel + const uint NUM_THREADS = 10; ///< number of threads trying to increment these counters + const uint MAX_PAUSE = 10000; ///< maximum delay implemented as empty counting loop + const uint MAX_SUM = 1000; ///< trigger when to finish incrementing + const uint MAX_INC = 10; ///< maximum increment on each step + + + + class Victim + : public Sync + { + volatile long cnt_[NUM_COUNTERS]; + volatile uint step_; ///< @note stored as instance variable + + void + pause () + { + Lock guard (this); // note recursive lock + + for ( uint i=0, lim=(rand() % MAX_PAUSE); ijoin(); + } + + void + start () + { + thread_ = Glib::Thread::create(sigc::mem_fun(*this, &HavocThread::doIt), true); + ASSERT (thread_); + } + }; + + } // (End) test classes and data.... + + + + + + + + + + + /********************************************************************** + * @test create multiple threads, all concurrently trying to increment + * a number of counters with random steps and random pauses. Without + * locking, the likely result will be differing counters. + * But because the class Victim uses an object level monitor to + * guard the mutations, the state should remain consistent. + * + * @see sync.hpp + */ + class SyncLocking_test : public Test + { + + virtual void + run (Arg) + { + if (!Glib::thread_supported()) + Glib::thread_init(); + + REQUIRE (ourVictim.checkAllEqual()); + { + HavocThread threads[NUM_THREADS]; + for (uint i=0; i=" << MAX_SUM << "\n"; + ourVictim.report(); + } + } + + }; + + + + /** Register this test class... */ + LAUNCHER (SyncLocking_test, "unit common"); + + + + } // namespace test + +} // namespace lumiera diff --git a/tests/lib/sync-waiting-test.cpp b/tests/lib/sync-waiting-test.cpp new file mode 100644 index 000000000..1cc8a9398 --- /dev/null +++ b/tests/lib/sync-waiting-test.cpp @@ -0,0 +1,164 @@ +/* + SyncWaiting(Test) - check the monitor object based wait/notification + + Copyright (C) Lumiera.org + 2008, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + 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. + + 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 02139, USA. + +* *****************************************************/ + + +#include "lib/test/run.hpp" +#include "include/error.hpp" + +#include "lib/sync.hpp" + +#include + +#include + +using std::cout; +using test::Test; + + +namespace lib { + namespace test { + + namespace { // private test classes and data... + + + /** Interface defining the basic interaction pattern for this test */ + class Token + { + public: + + /** blocking concurrent operation */ + virtual void getIt() =0; + + /** start the notification chain */ + virtual void provide (uint val) =0; + + /** harvesting the result...*/ + uint result () { return sum_; } + + + protected: + volatile uint sum_, input_; + + virtual ~Token() {} + + Token() : sum_(0), input_(0) {} + }; + + + class SyncOnBool + : public Token, + public Sync + { + bool got_new_data_; + + public: + SyncOnBool() : got_new_data_ (false) {} + + void getIt() + { + Lock(this).wait (got_new_data_); + sum_ += input_; + } + + void provide (uint val) + { + Lock sync(this); + input_ = val; + got_new_data_ = true; + sync.notifyAll(); + } + }; + + } // (End) test classes and data.... + + + + + + + + + + + /**************************************************************************** + * @test concurrent waiting and notification, implemented via object monitor. + * This test covers the second part of the monitor pattern, which builds upon + * the locking part an additionally uses an embedded condition. We provide + * several pre-configured ways of specifying the condition to wait upon. + * + * @see sync.hpp + */ + class SyncWaiting_test : public Test + { + + virtual void + run (Arg) + { + if (!Glib::thread_supported()) + Glib::thread_init(); + + SyncOnBool use_sync_var; + waitPingPong (use_sync_var); + } + + + /** + * Helper actually performing the test: + * creates two threads and let them block and wait cross-wise. + * @param tok object containing the monitor and condition to be tested. + */ + void + waitPingPong (Token& tok) + { + Glib::Thread *ping, *pong; + + ping = Glib::Thread::create(sigc::mem_fun(tok, &Token::getIt), true); + pong = Glib::Thread::create(sigc::mem_fun(tok, &Token::getIt), true); + + ASSERT (ping); + ASSERT (pong); + ASSERT (0 == tok.result()); + + sleep (1); // if the threads don't block correctly, they've missed their chance by now... + + // kick off the notification cascade... + uint val = (rand() % 1000); + tok.provide (val); + + // wait for the two Threads to finish their handshake + pong->join(); + ping->join(); + + ASSERT (2*val == tok.result()); + } + }; + + + + /** Register this test class... */ + LAUNCHER (SyncWaiting_test, "unit common"); + + + + } // namespace test + +} // namespace lumiera