diff --git a/include/loki/LevelMutex.h b/include/loki/LevelMutex.h index e2ff83f..43cf336 100644 --- a/include/loki/LevelMutex.h +++ b/include/loki/LevelMutex.h @@ -1,1206 +1,1208 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// LevelMutex facility for the Loki Library -// Copyright (c) 2008 Richard Sposato -// The copyright on this file is protected under the terms of the MIT license. -// -// Permission to use, copy, modify, distribute and sell this software for any -// purpose is hereby granted without fee, provided that the above copyright -// notice appear in all copies and that both that copyright notice and this -// permission notice appear in supporting documentation. -// -// The author makes no representations about the suitability of this software -// for any purpose. It is provided "as is" without express or implied warranty. -// -//////////////////////////////////////////////////////////////////////////////// - -// $Id$ - -/// @file LevelMutex.h Defines classes and functions for LevelMutex facility. - -#ifndef LOKI_LEVEL_MUTEX_H_INCLUDED -#define LOKI_LEVEL_MUTEX_H_INCLUDED - - -// ---------------------------------------------------------------------------- - -#include -#include -#include - -#if defined( _MSC_VER ) - #include -#else - #include -#endif - - -/** @par thread_local Keyword - The mutexes require compilers to provide thread local storage - meaning each - thread gets its own copy of the data. The next version of C++ will have a - new keyword, thread_local for that purpose. Some existing compilers already - provide thread local storage using different syntax, so these lines use - thread_local to mimic that syntax. If your compiler provides thread local - storage but using different syntax besides "thread_local", you may want to - modify these lines. If your compiler does not support thread local storage, - you can't use LevelMutex. - */ -#ifndef LOKI_THREAD_LOCAL - #if defined( _MSC_VER ) - #if ( _MSC_VER >= 1300 ) - #define LOKI_THREAD_LOCAL __declspec( thread ) - #else - #error "Only Visual Studio versions 7.0 and after supported." - #endif - - #elif ( __GNUC__ ) - #define LOKI_THREAD_LOCAL __thread - - #else - #warning "Check if your compiler provides thread local storage." - #define LOKI_THREAD_LOCAL thread_local - #endif -#endif - -#if defined( DEBUG ) || defined( _DEBUG ) - #define LOKI_MUTEX_DEBUG_CODE( x ) x -#else - #define LOKI_MUTEX_DEBUG_CODE( x ) -#endif - - -namespace Loki -{ - - -// ---------------------------------------------------------------------------- - -class MutexErrors -{ -public: - - /// @enum Type Possible error conditions detected by LevelMutex functions. - enum Type - { - Success = 0, ///< Operation occurred correctly. - NoProblem, ///< Pre-lock and pre-unlock checks passed. - WrongLevel, ///< All mutexes in container must have same level. - LevelTooLow, ///< Trying to unlock a mutex lower than current level. - LevelTooHigh, ///< Trying to lock a mutex higher than current level. - TryFailed, ///< TryLock call failed to lock mutex. - NullMutexPointer, ///< Container has a NULL pointer in it. - DuplicateMutex, ///< Container must have unique pointers - no duplicates. - EmptyContainer, ///< Container must have at least 1 pointer in it. - AlreadyLocked, ///< TryLock call failed because mutex already locked. - WasntLocked, ///< Unlock failed because mutex was not even locked. - NotRecentLock, ///< Mutex in container was not recently locked by this thread. - NotLockedByThread, ///< Can't unlock a mutex not locked by this thread. - MultiUnlockFailed, ///< MultiUnlock can't unlock at least 1 mutex in container. - TimedOut, ///< Wait time elapsed without locking mutex. - TooMuchRecursion, ///< Tried to relock a PThread mutex which is not re-entrant. - NotInitialized, ///< Tried to lock a PThread mutex which did not get setup. - AlreadyInitialized, ///< PThread mutex initialized before ctor called. - InvalidAttribute, ///< PThread mutex improperly initialized. - InvalidAddress, ///< Bad pointer used to initialize a PThread mutex. - ExceptionThrown, ///< Exception caught in mutex operation. - MayDeadlock, ///< Locking this mutex may cause a deadlock. - OtherError ///< Unknown error occurred. - }; -}; - -// ---------------------------------------------------------------------------- - -/** @class LevelMutexInfo - This monolithic base class stores common info for a template class used to - control mutexes. The template class, LevelMutex, is policy-based class. - - @par Implementation - Each thread has a list of mutexes it locked. When a mutex first gets locked, it - gets added to the head of the list. If locked again, LevelMutex merely increments - a count. When unlocked, the count gets decremented until it reaches zero, and - then it gets removed from the list. Each mutex has a pointer to the mutex most - recently locked by the current thread. The current level of a thread is always - the level of the most recently locked mutex, or UnlockedLevel if the thread does - not have any mutexes locked now. A mutex is considered "recently" locked if it is at - the head of the list, or the same level as the current mutex and also locked by the - current thread. - - @par Class Invariants - This class maintains invariants for each LevelMutexInfo so that no function - calls corrupt a mutex. Each function makes a call to IsValid at the start so - that LevelMutex knows it acts on valid internal data. Many functions call - IsValid again when they return to insure the function did not leave any data in - an invalid state. The exit call to IsValid occurs through a tiny helper class - called Checker to insure all data remain valid even when exceptions occur. - Another helper class, MutexUndoer, unlocks mutexes in a container if an - exception occurs during calls to MultiLock. - - @par Error Results - Many functions return an enum value to indicate an error status. Many enum values - indicate errors detected within LevelMutex, but some indicate errors found in policy - classes, SpinLevelMutex and SleepLevelMutex. - */ - -class LevelMutexInfo -{ -public: - - /** Level for thread that has not locked any mutex. Maximum possible level - for a mutex is UnlockedLevel-1; No mutex may have a level of UnlockedLevel. - */ - static const unsigned int UnlockedLevel = 0xFFFFFFFF; - - /// Container for locking multiple mutexes at once. - typedef ::std::vector< volatile LevelMutexInfo * > MutexContainer; - typedef MutexContainer::iterator LevelMutexContainerIter; - typedef MutexContainer::const_iterator LevelMutexContainerCIter; - typedef MutexContainer::reverse_iterator LevelMutexContainerRIter; - typedef MutexContainer::const_reverse_iterator LevelMutexContainerCRIter; - - /** Locks several mutexes at once. Requires O(m + n*n) actions where m is the - number of mutexes currently locked by the thread and n is the number of mutexes - in the container. This provides strong exception safety. If an exception occurs, - any mutexes that were locked during this call will get unlocked. - @param mutexes Container of pointers to mutexes. Container must have at - least 1 mutex, all mutexes must have the same level, no NULL pointers, and all - mutexes must not exceed the thread's current level. This sorts the container - by address order. - @return Enum value indicating success or error. - */ - static MutexErrors::Type MultiLock( MutexContainer & mutexes ); - - /** Locks several mutexes at once. Requires O(m + n*n + n*t) actions where m is - the number of mutexes currently locked by the thread, n is the number of mutexes - in the container, and t is the wait time for each mutex. This provides strong - exception safety. If an exception occurs, any mutexes that were locked during - this call will ge unlocked. - @param mutexes Container of pointers to mutexes. Container must have at - least 1 mutex, all mutexes must have the same level, no NULL pointers, and all - mutexes must not exceed the thread's current level. This sorts the container - by address order. - @param milliSeconds Amount of time to wait for each mutex. - @return Enum value indicating success or error. - */ - static MutexErrors::Type MultiLock( MutexContainer & mutexes, - unsigned int milliSeconds ); - - /** Unlocks several mutexes at once. Requires O(m) actions where m is the number of - mutexes in the container. This provides strong exception safety. If an exception - occurs when unlocking one mutex, other mutexes in the container get unlocked anyway. - @param mutexes Container of pointers to mutexes. Container must have at least 1 - mutex, all mutexes must have the same level, no NULL pointers, and all mutexes must - be locked by the current thread. This sorts the container dby address order. - @return Enum value indicating success or error. - */ - static MutexErrors::Type MultiUnlock( MutexContainer & mutexes ); - - /** Gives pointer to most recently locked mutex, or NULL if nothing locked. - The pointer is for a const mutex so the mutex can't be modified inappropriately. - The pointer is for a volatile mutex so callers can call volatile member - functions to get info about the mutex. - */ - static const volatile LevelMutexInfo * GetCurrentMutex( void ); - - /// Returns the level of this mutex. - inline unsigned int GetLevel( void ) const volatile { return m_level; } - - /// Returns true if this mutex was locked at least once. - inline bool IsLocked( void ) const volatile { return ( 0 < m_count ); } - - /// Returns count of how many times this mutex got locked. - inline unsigned int GetLockCount( void ) const volatile { return m_count; } - - /// Returns pointer to mutex previously locked by the thread which locked this. - inline const volatile LevelMutexInfo * GetPrevious( void ) const volatile - { - return m_previous; - } - - /** Tries to lock mutex, and returns immediately if mutex already locked by - another thread. It will return immediately with a value of AlreadyLocked - if the mutex was locked by a different thread. It may throw an exception - or assert when errors occur if the ErrorPolicy class implements that behavior. - @return An error condition if any occurred, else Success. - */ - virtual MutexErrors::Type TryLock( void ) volatile = 0; - - /** Blocking call will attempt to lock mutex and wait until it can lock. - This may throw an exception if the lock failed or an error occurred - if - that is what the error policy specifies. - @return An error condition if any occurred, else Success. - */ - virtual MutexErrors::Type Lock( void ) volatile = 0; - - /** Attempts to lock mutex, but only waits for a limited amount of time - before it gives up. Will return quickly if an error occurs before any - attempt to lock. This may throw an exception if the lock failed or an - error occurred - if that is what the error policy specifies. - @param milliSeconds How long to wait. - @return An error condition if any occurred, else Success. - */ - virtual MutexErrors::Type Lock( unsigned int milliSeconds ) volatile = 0; - - /** Unlocks the mutex, or returns an error condition. This may throw an - exception if the lock failed or an error occurred - if that is what the - error policy specifies. - @return An error condition if any occurred, else Success. - */ - virtual MutexErrors::Type Unlock( void ) volatile = 0; - - /** Returns true if this mutex was locked by current thread, and level is the same - as the current thread's level. Which means this was the most recently locked - mutex, or it was locked along with several others of the same level recently. - */ - bool IsRecentLock( void ) const volatile; - - /** Returns true if this mutex was locked within the last count mutexes. - @param count How many recent mutexes to look through to find this mutex. - */ - bool IsRecentLock( unsigned int count ) const volatile; - - /// Returns true if this was locked by current thread. - bool IsLockedByCurrentThread( void ) const volatile; - - /// Returns true if this was locked by another thread. - bool IsLockedByAnotherThread( void ) const volatile; - -protected: - - /** @class Checker Performs validity check on mutex to insure no class invariants - were violated inside any member function. This class only gets used in debug - builds, and any instance of it gets optimized away in release builds. A checker - is created inside many of member functions so that it's destructor gets called - when the function exits. It determines if any class invariants were violated - during the function call. - */ - class Checker - { - public: - inline explicit Checker( const volatile LevelMutexInfo * mutex ) : - m_mutex( mutex ) {} - inline ~Checker( void ) { m_mutex->IsValid(); } - private: - Checker( void ); - Checker( const Checker & ); - Checker & operator = ( const Checker & ); - const volatile LevelMutexInfo * m_mutex; - }; - - /** @class MutexUndoer - Undoes actions by MultiLock if an exception occurs. It keeps track of - which mutexes in a container got locked, and if an exception occurs, then - the destructor unlocks them. If MultiLock succeeds, then it cancels the - undoer so nothing gets unlocked inadvertently. - */ - class MutexUndoer - { - public: - - explicit MutexUndoer( MutexContainer & mutexes ); - ~MutexUndoer( void ); - void SetPlace( LevelMutexContainerIter & here ); - void Cancel( void ); - - private: - - MutexUndoer( void ); - MutexUndoer( const MutexUndoer & ); - MutexUndoer & operator = ( const MutexUndoer & ); - - MutexContainer & m_mutexes; - LevelMutexContainerIter m_here; - }; - - /** Returns true if linked-list of locked mutexes in this thread is valid. - Which means the list has no loops, and each previous mutex on the list has a - higher or same level as the current mutex. Called by IsValid. - */ - static bool IsValidList( void ); - - /** This is the only available constructor, and it forces any derived class to set - a level for each mutex. - */ - explicit LevelMutexInfo( unsigned int level ); - - /// The destructor only gets called by the derived class. - virtual ~LevelMutexInfo( void ); - - MutexErrors::Type PreLockCheck( bool forTryLock ) volatile; - - MutexErrors::Type PreUnlockCheck( void ) volatile; - - /** This gets called after each call to DoLock and DoTryLock to make sure the data - members in this object get set correctly. - */ - void PostLock( void ) volatile; - - /// Gets called just before an attempt to unlock a mutex. - void PreUnlock( void ) volatile; - - /// Called to relock a mutex already locked by the current thread. - void IncrementCount( void ) volatile; - - /// Called to unlock a mutex locked multiple times by the current thread. - void DecrementCount( void ) volatile; - - /** Returns true if no class invariant broken, otherwise asserts. This function - only gets called in debug builds. - */ - bool IsValid( void ) const volatile; - -private: - - /// Copy constructor is not implemented. - LevelMutexInfo( const LevelMutexInfo & ); - /// Copy-assignment operator is not implemented. - LevelMutexInfo & operator = ( const LevelMutexInfo & ); - - /** Called only by MultiLock & MultiUnlock to pass a result through an - error checking policy. - @param result What error condition to check. - @return Result or assertion or an exception - depending on error policy. - */ - virtual MutexErrors::Type DoErrorCheck( MutexErrors::Type result ) const volatile = 0; - - /// Called only by MultiLock to Lock each particular mutex within a container. - virtual MutexErrors::Type LockThis( void ) volatile = 0; - - /** Called only by MultiLock to lock each particular mutex within a container. - @param milliSeconds How much time to wait before giving up on locking a mutex. - */ - virtual MutexErrors::Type LockThis( unsigned int milliSeconds ) volatile = 0; - - /// Called only by MultiUnlock to unlock each particular mutex within a container. - virtual MutexErrors::Type UnlockThis( void ) volatile = 0; - - /// Pointer to singly-linked list of mutexes locked by the current thread. - static LOKI_THREAD_LOCAL volatile LevelMutexInfo * s_currentMutex; - - /// Level of this mutex. - const unsigned int m_level; - - /// How many times this mutex got locked. - unsigned int m_count; - - /// Pointer to mutex locked before this one. - volatile LevelMutexInfo * m_previous; - -}; - -// ---------------------------------------------------------------------------- - -/** @class ThrowOnAnyMutexError - Implements the ErrorPolicy for LevelMutex and throws an exception for any - error condition. Only allows MutexErrors::Success and MutexErrors::NoProblem - to get through. Useful for release builds. - */ -class ThrowOnAnyMutexError -{ -public: - static MutexErrors::Type CheckError( MutexErrors::Type error, - unsigned int level ); -}; - -// ---------------------------------------------------------------------------- - -/** @class ThrowOnBadDesignMutexError - Implements the ErrorPolicy for LevelMutex and throws an exception if the error - indicates the programmer did not levelize the calls to mutexes. Otherwise - returns the error result. Useful for release builds. - */ -class ThrowOnBadDesignMutexError -{ -public: - static MutexErrors::Type CheckError( MutexErrors::Type error, - unsigned int level ); -}; - -// ---------------------------------------------------------------------------- - -/** @class AssertAnyMutexError - Implements the ErrorPolicy for LevelMutex and asserts for any error condition. - Only allows MutexErrors::Success and MutexErrors::NoProblem to get through. - Useful for testing mutexes in debug builds. - */ -class AssertAnyMutexError -{ -public: - static inline MutexErrors::Type CheckError( MutexErrors::Type error, - unsigned int level ) - { - (void)level; - assert( ( error == MutexErrors::Success ) - || ( error == MutexErrors::NoProblem ) ); - return error; - } -}; - -// ---------------------------------------------------------------------------- - -/** @class AssertBadDesignMutexError - Implements the ErrorPolicy for LevelMutex and asserts if the error - indicates the programmer did not levelize the calls to mutexes. Otherwise - returns the error result. Useful for testing mutexes in debug builds. - */ -class AssertBadDesignMutexError -{ -public: - static inline MutexErrors::Type CheckError( MutexErrors::Type error, - unsigned int level ) - { - (void)level; - assert( ( error != MutexErrors::LevelTooHigh ) - && ( error != MutexErrors::LevelTooLow ) ); - return error; - } -}; - -// ---------------------------------------------------------------------------- - -/** @class JustReturnMutexError - Implements the ErrorPolicy for LevelMutex and does nothing no matter how bad - the error condition. Only recommended use is for automated unit-testing of - mutex policies. - */ -class JustReturnMutexError -{ -public: - static inline MutexErrors::Type CheckError( MutexErrors::Type error, - unsigned int level ) - { - (void)level; - return error; - } -}; - -// ---------------------------------------------------------------------------- - -/** @class NoMutexWait - Implements the WaitPolicy for LevelMutex. Does nothing at all so it turns - all wait loops into spin loops. Useful for low-level mutexes. - */ -class NoMutexWait -{ -public: - static inline void Wait( void ) {} -}; - -// ---------------------------------------------------------------------------- - -/** @class MutexSleepWaits - Implements the WaitPolicy for LevelMutex. Sleeps for a moment so thread won't - consume idle CPU cycles. Useful for high-level mutexes. - */ -class MutexSleepWaits -{ -public: - static void Wait( void ); - static unsigned int sleepTime; -}; - -// ---------------------------------------------------------------------------- - -/** @class SpinLevelMutex - Implements a spin-loop to wait for the mutex to unlock. Since this class makes - the thread wait in a tight spin-loop, it can cause the thread to remain busy - while waiting and thus consume CPU cycles. For that reason, this mutex is best - used only for very low-level resources - especially resources which do not - require much CPU time to exercise. Rule of thumb: Use this only if all actions - on the resource consume a very small number of CPU cycles. Otherwise, use the - SleepLevelMutex instead. - */ -class SpinLevelMutex -{ -public: - - /// Constructs a spin-level mutex. - explicit SpinLevelMutex( unsigned int level ); - - /// Destructs the mutex. - virtual ~SpinLevelMutex( void ); - - virtual MutexErrors::Type Lock( void ) volatile; - - virtual MutexErrors::Type TryLock( void ) volatile; - - virtual MutexErrors::Type Unlock( void ) volatile; - - inline unsigned int GetLevel( void ) const volatile { return m_level; } - -private: - - /// Copy constructor is not implemented. - SpinLevelMutex( const SpinLevelMutex & ); - /// Copy-assignment operator is not implemented. - SpinLevelMutex & operator = ( const SpinLevelMutex & ); - -#if defined( _MSC_VER ) - #if ( _MSC_VER >= 1300 ) - /// The actual mutex. - CRITICAL_SECTION m_mutex; - #else - #error "Only Visual Studio versions 7.0 and after supported." - #endif - -#elif ( __GNUC__ ) - /// The actual mutex. - pthread_mutex_t m_mutex; - -#else - #error "Check if any mutex libraries are compatible with your compiler." -#endif - - /// Keep a copy of the mutex level around for error reporting. - const unsigned int m_level; - -}; // end class SpinLevelMutex - -// ---------------------------------------------------------------------------- - -/** @class SleepLevelMutex - Implements a sleeping loop to wait for the mutex to unlock. - - @par Purpose - Since this class puts the thread to sleep for short intervals, you can use this - class for most of your mutexes. Especially for locking any high level resources - where any one operation on the resouce consumes many CPU cycles. The purpose of - this mutex is to reduce the number of CPU cycles spent in idle loops. All - SleepLevelMutex's should have higher levels than all your SpinLevelMutex's. - - @par Dependence on SpinLevelMutex - This utilizes SpinLevelMutex so it does not have to re-implement the DoTryLock - and DoUnlock functions the same way. All it really needs is a DoLock function - and the amount of time it should sleep if an attempt to lock a function fails. - */ -class SleepLevelMutex : public SpinLevelMutex -{ -public: - - /** Constructs a levelized mutex that puts threads to sleep while they wait - for another thread to unlock the mutex. - @param level Level of this mutex. - */ - explicit SleepLevelMutex( unsigned int level ); - - /// Destructs the mutex. - virtual ~SleepLevelMutex( void ); - - inline unsigned int GetSleepTime( void ) const volatile { return m_sleepTime; } - - inline void SetSleepTime( unsigned int sleepTime ) volatile - { - if ( 0 != sleepTime ) - m_sleepTime = sleepTime; - } - -#if defined( _MSC_VER ) - inline bool GetWakable( void ) const volatile { return m_wakable; } - inline void SetWakable( bool wakable ) volatile { m_wakable = wakable; } -#endif - - /** Attempts to lock a mutex, and if it fails, then sleeps for a while - before attempting again. - */ - virtual MutexErrors::Type Lock( void ) volatile; - -private: - - /// Default constructor is not implemented. - SleepLevelMutex( void ); - /// Copy constructor is not implemented. - SleepLevelMutex( const SleepLevelMutex & ); - /// Copy-assignment operator is not implemented. - SleepLevelMutex & operator = ( const SleepLevelMutex & ); - -#if defined( _MSC_VER ) - #if ( _MSC_VER >= 1300 ) - /// True if operating system may wake thread to respond to events. - bool m_wakable; - #else - #error "Only Visual Studio versions 7.0 and after supported." - #endif -#endif - - /// How many milli-seconds to sleep before trying to lock mutex again. - unsigned int m_sleepTime; - -}; // end class SleepLevelMutex - -// ---------------------------------------------------------------------------- - -/** @class LevelMutex - Levelized mutex class prevents deadlocks by requiring programs to lock mutexes in - the same order, and unlock them in reverse order. This is accomplished by forcing - each mutex to have a level and forcing code to lock mutexes with higher levels - before locking mutexes at lower levels. If you want to lock several mutexes, they - must be locked in decreasing order by level, or if they are all of the same level, - then locked by LevelMutex::MultiLock. - - @par Features - - Immune: Very unlikely to deadlock since all mutexes are locked in the same - order and unlocked in reverse order. - - Scalable: Can handle any number of mutexes. - - Efficient: Many operations occur in constant time, and most operations require - no more than O(m) steps. - - Exception safe: All operations provide strong safety or don't throw. - - Extendable: Can work with existing mutexes through policy-based design. - - Easily Extended: Derived classes only need to implement 5 functions and a mutex - to get all the features of this class. - - Re-Entrant: Allows for re-entrancy even if mutexes in policy classes don't. - - Cost-Free: No resource allocations occur in LevelMutex - although user-defined - policy classes may allocate resources. - - Compact: Each LevelMutex object is small. - - Portable: As long as your compiler and libraries can meet the requirements. - - Robust: Maintains data integrity even if exceptions occur in policy classes. - - Affording: Several functions provide information about a mutex which allows - client code to easily choose correct actions. - - @par Requirements - - Your compiler must allow for thread-specific data. - - You must have a threading or mutex library. - - @par Policy-Based Design - This class hosts 3 policies and a default level. The policy-based design allows - users to write their own policies to extend the behaviors of LevelMutex. The - paragraphs below say how to design a class for each policy. - - MutexPolicy The mutex policy class. - - defaultLevel A level for existing client code that calls a default constructor. - - ErrorPolicy How the mutex should handle error conditions. - - WaitPolicy Whether a thread should wait, and how long in some internal loops. - - @par MutexPolicy - A policy class that wraps a low-level mutex. Loki provides two policy classes - for the actual mutex (SpinLevelMutex and SleepLevelMutex), both of which wrap - either pthreads or the Windows CRITICAL_SECTION. If you want to use a mutex - mechanism besides one of those, then all you have to do is provide a class - which wraps the mutex and implements these functions. - explicit SpinLevelMutex( unsigned int level ); - virtual ~SpinLevelMutex( void ); - virtual MutexErrors::Type Lock( void ) volatile; - virtual MutexErrors::Type TryLock( void ) volatile; - virtual MutexErrors::Type Unlock( void ) volatile; - Indeed, since the base class does most of the work, and provides all the interace - and functionality to client classes, a derived class has very few requirements. - It only needs to implement a single constructor, the destructor, some virtual - functions, and whatever data members it requires. You don't actually need to - declare those functions as virtual if the policy class is not a base or child - class. In the parlance of design patterns, LevelMutex is a Template, and the - MutexPolicy is a Strategy. - - @par DefaultLevel - The template class requires a default level to use inside the default constructor. - Some existing code calls instantiates mutexes with a default constructor, so the - mutex must know what level to use there. Please do not use zero or UnlockedLevel - as the default level. - - @par ErrorPolicy - This policy specifies how to handle error conditions. The mutexes can return - errors, assert, or throw exceptions. I recommend that debug code use asserts, - release code use exceptions, and unit-testing code just return errors. The - error policy class only needs to implement one function: - static MutexErrors::Type CheckError( MutexErrors::Type error, unsigned int level ); - - @par WaitPolicy - This states whether the mutex should wait within some tight internal loops, - how the waiting is done, and for how long. A wait policy class could sleep, - do nothing, check if other objects need attention, or check if the program - received events or notices from the operating system. It only needs to - implement one function: - static void Wait( void ); - - @par Per-Function Usage - If you implement a function with a static local mutex, then you have to insure - the function is not called from a lower level via call-backs, virtual functions in - interface classes. If the function does get called from a lower level, you are - setting up a potential deadlock. LevelMutex will detect that by checking the - current level and the local mutex's level, so it will refuse to lock the local mutex. - - @par Per-Object Usage - If you use a mutex as a data member of an object to protect that object, then I - recommend specifying which functions are volatile and which are not, and then only - use the mutex within the volatile functions. You may also want to provide accessor - functions so that client code can lock and unlock the mutex either to allow for - calling multiple operations without having to lock and unlock before and after each - operation, or so they can lock it along with several other objects at the same - level. - - @par Per-Class Usage - If you make a static data member within a class, you can use that to lock any - resources shared by those objects, or to require threads to act on only one object - at a time. You may also want to provide static accessor functions so that client - code can lock several other resources at the same level. - */ - -template -< - class MutexPolicy, - unsigned int DefaultLevel, - class ErrorPolicy = ::Loki::ThrowOnBadDesignMutexError, - class WaitPolicy = ::Loki::NoMutexWait -> -class LevelMutex : public LevelMutexInfo -{ -public: - - typedef typename ErrorPolicy EP; - typedef typename WaitPolicy WP; - typedef typename MutexPolicy MP; - - /** This constructor allows callers to replace the default level with another - value. It also acts as the default constructor for existing code which uses - default construction for mutexes. This is the only time the DefaultLevel - template parameter gets used. - */ - explicit LevelMutex( unsigned int level = DefaultLevel ) : - LevelMutexInfo( level ), - m_mutex( level ) - { - assert( IsValid() ); - } - - /// The destructor. - ~LevelMutex( void ) - { - assert( IsValid() ); - } - - /** These functions allow callers to access the mutex in case they need to - modify specific values in the MutexPolicy (e.g. - sleep time, functors to - call as tasks, etc...) There is one function for every combination of - const and volatile qualifiers so callers get a reference to a MutexPolicy - with the proper qualifiers. - */ - inline const volatile MutexPolicy & GetMutexPolicy( void ) const volatile { return m_mutex; } - inline volatile MutexPolicy & GetMutexPolicy( void ) volatile { return m_mutex; } - inline const MutexPolicy & GetMutexPolicy( void ) const { return m_mutex; } - inline MutexPolicy & GetMutexPolicy( void ) { return m_mutex; } - - virtual MutexErrors::Type TryLock( void ) volatile - { - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - MutexErrors::Type result = LevelMutexInfo::PreLockCheck( true ); - if ( MutexErrors::Success == result ) - return MutexErrors::Success; - else if ( MutexErrors::AlreadyLocked == result ) - return result; - else if ( MutexErrors::NoProblem != result ) - return EP::CheckError( result, GetLevel() ); - - assert( 0 == LevelMutexInfo::GetLockCount() ); - result = m_mutex.TryLock(); - if ( MutexErrors::Success != result ) - return EP::CheckError( result, GetLevel() ); - LevelMutexInfo::PostLock(); - - return MutexErrors::Success; - } - - virtual MutexErrors::Type Lock( void ) volatile - { - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - MutexErrors::Type result = LevelMutexInfo::PreLockCheck( false ); - if ( MutexErrors::Success == result ) - return MutexErrors::Success; - else if ( MutexErrors::NoProblem != result ) - return EP::CheckError( result, GetLevel() ); - - assert( !LevelMutexInfo::IsLockedByCurrentThread() ); - result = m_mutex.Lock(); - if ( MutexErrors::Success != result ) - return EP::CheckError( result, GetLevel() ); - PostLock(); - - return MutexErrors::Success; - } - - virtual MutexErrors::Type Lock( unsigned int milliSeconds ) volatile - { - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - MutexErrors::Type result = LevelMutexInfo::PreLockCheck( false ); - if ( MutexErrors::Success == result ) - return MutexErrors::Success; - else if ( MutexErrors::NoProblem != result ) - return EP::CheckError( result, GetLevel() ); - - assert( !LevelMutexInfo::IsLockedByCurrentThread() ); - clock_t timeOut = clock() + milliSeconds; - while ( clock() < timeOut ) - { - WP::Wait(); - result = m_mutex.TryLock(); - switch ( result ) - { - case MutexErrors::Success: - { - PostLock(); - return MutexErrors::Success; - } - case MutexErrors::AlreadyLocked: - return MutexErrors::AlreadyLocked; - case MutexErrors::TryFailed: - break; - default: - return EP::CheckError( result, GetLevel() ); - } - } - - return MutexErrors::TimedOut; - } - - virtual MutexErrors::Type Unlock( void ) volatile - { - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - MutexErrors::Type result = LevelMutexInfo::PreUnlockCheck(); - if ( MutexErrors::Success == result ) - return MutexErrors::Success; - else if ( MutexErrors::NoProblem != result ) - return EP::CheckError( result, GetLevel() ); - - LevelMutexInfo::PreUnlock(); - result = MutexErrors::OtherError; - try - { - result = m_mutex.Unlock(); - if ( MutexErrors::Success != result ) - PostLock(); - } - catch ( ... ) - { - PostLock(); - result = MutexErrors::ExceptionThrown; - } - - return result; - } - -private: - - /// Copy constructor is not implemented since mutexes don't get copied. - LevelMutex( const LevelMutex & ); - /// Copy-assignment operator is not implemented since mutexes don't get copied. - LevelMutex & operator = ( const LevelMutex & ); - - virtual MutexErrors::Type DoErrorCheck( MutexErrors::Type result ) const volatile - { - return EP::CheckError( result, GetLevel() ); - } - - /** Called only by MultiLock to lock each particular mutex within a container. - This does not do pre-lock error checking since MultiLock does that. Since - this skips the error checking, that means that callers of LevelMutex should - not call this function directly, and so this will not be publicly available. - @return Error status indicating success or reason for failure. - */ - virtual MutexErrors::Type LockThis( void ) volatile - { - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - assert( this != LevelMutexInfo::GetCurrentMutex() ); - - const MutexErrors::Type result = m_mutex.Lock(); - if ( MutexErrors::Success != result ) - return result; - PostLock(); - - return MutexErrors::Success; - } - - /** Called only by MultiLock to lock each particular mutex within a container. - This does not do pre-lock error checking since MultiLock does that. Since - this skips the error checking, callers of LevelMutex should not call this - function directly, and so this will not be publicly available. - @param milliSeconds How much time to wait before giving up on locking a mutex. - @return Error status indicating success or reason for failure. - */ - virtual MutexErrors::Type LockThis( unsigned int milliSeconds ) volatile - { - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - clock_t timeOut = clock() + milliSeconds; - while ( clock() < timeOut ) - { - WP::Wait(); - const bool locked = ( MutexErrors::Success == m_mutex.TryLock() ); - if ( locked ) - { - PostLock(); - return MutexErrors::Success; - } - } - - return MutexErrors::TimedOut; - } - - /** Called only by MultiUnlock to unlock each mutex within a container. - This does not do pre-unlock error checking since MultiLock does that. Since - this skips the error checking, callers of LevelMutex should not call this - function directly, and so this will not be publicly available. - @return Error status indicating success or reason for failure. - */ - virtual MutexErrors::Type UnlockThis( void ) volatile - { - assert( IsValid() ); - assert( NULL != LevelMutexInfo::GetCurrentMutex() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - if ( 1 < LevelMutexInfo::GetLockCount() ) - { - LevelMutexInfo::DecrementCount(); - return MutexErrors::Success; - } - - LevelMutexInfo::PreUnlock(); - MutexErrors::Type result = m_mutex.Unlock(); - - return result; - } - - /// An instance of an unleveled mutex wrapped to match LevelMutex's needs. - MutexPolicy m_mutex; - -}; // end class LevelMutex - -// ---------------------------------------------------------------------------- - -/** Returns level of most recently locked mutex by this thread, or UnlockedLevel - if no mutexes are locked. Runs in constant time, and never throws exceptions. - */ -unsigned int GetCurrentThreadsLevel( void ); - -/** Returns count of how mutexes the current thread locked. Requires O(m) - actions where m is the number of mutexes in the thread. Never throws exceptions. - */ -unsigned int CountMutexesInCurrentThread( void ); - -/** Returns count of how mutexes the current thread locked. The lock count - exceeds the number of mutexes locked by current thread if any mutex got locked - more than once. Requires O(m) actions where m is the number of mutexes in the - thread. Never throws exceptions. - */ -unsigned int CountLocksInCurrentThread( void ); - -/** Returns count of mutexes locked by current thread which have the same level - as GetCurrentThreadsLevel. Requires O(m) actions where m is the number of - mutexes in the thread at current level. Never throws exceptions. - */ -unsigned int CountMutexesAtCurrentLevel( void ); - -/** Determines if container of mutexes matches the recently locked mutexes. - If they do match, it returns success, otherwise an error condition. - */ -MutexErrors::Type DoMutexesMatchContainer( const LevelMutexInfo::MutexContainer & mutexes ); - -// ---------------------------------------------------------------------------- - -/** @class MutexException - Exception class used to throw error statuses about LevelMutex's up to code that - can respond to mutex problems. This class exists because it conveys more info - about the error condition than just ::std::exception. - */ -class MutexException : public ::std::exception -{ -public: - - /** Constructs an exception which stores information about a mutex and the - reason an attempt to use a mutex failed. - */ - MutexException( const char * message, unsigned int level, MutexErrors::Type reason ); - - /// Copy constructor performs a member-by-member copy of an exception. - MutexException( const MutexException & that ) throw (); - - /// Copy-assignment operator performs a member-by-member copy of an exception. - MutexException & operator = ( const MutexException & that ) throw (); - - /// Destroys the exception. - virtual ~MutexException( void ); - - /// Returns a simple message about which operation failed. - virtual const char * what( void ) const; - - /// Returns level of mutex(es) used when problem occurred. - unsigned int GetLevel( void ) const { return m_level; } - - /// Returns an error status for why operation failed. - MutexErrors::Type GetReason( void ) const { return m_reason; } - -private: - - /// Default constructor is not implemented. - MutexException( void ) throw (); - - /// Simple message about operation that failed. - const char * m_message; - /// Level of mutex(es) used when problem occurred. - unsigned int m_level; - /// Error status for why operation failed. - MutexErrors::Type m_reason; - -}; // end class MutexException - -// ---------------------------------------------------------------------------- - -/** @class MutexLocker - You can place an instance of this as a local variable inside a function to lock - a single mutex. It will lock the mutex if no error occurs, or throw if one - does happen. When the function ends, the destructor will determine if it needs - to unlock the mutex. This RAII technique insures the mutex gets unlocked even - when exceptions occur. - */ -class MutexLocker -{ -public: - - /** Creates an object to lock an unlock a mutex for a function. This - will throw if an attempt to lock the mutex fails. - @param mutex Reference to the mutex. - @param lock True if function wants to lock the mutex as this gets - constructed. - */ - explicit MutexLocker( volatile LevelMutexInfo & mutex, bool lock = true ); - - /** Creates an object to lock an unlock a mutex for a function. This waits - a specified amount of time for another thread to unlock the mutex if it is - locked. This will throw if an attempt to lock the mutex fails. - @param mutex Reference to the mutex. - @param milliSeconds Amount of time to wait for another thread to unlock - the mutex. - @param lock True if function wants to lock the mutex as this gets - constructed. - */ - MutexLocker( volatile LevelMutexInfo & mutex, unsigned int milliSeconds, - bool lock = true ); - - /// Destructs the locker, and determines if it needs to unlock the mutex. - ~MutexLocker( void ); - - /** You can call this to lock (or relock) a mutex. In theory, you can lock - and unlock a mutex several times within a function in order to give other - threads access to a resource while this function does not need it. - @return True if mutex is locked by this, else false if not locked. - */ - bool Lock( void ); - - /** You can call this to unlock a mutex before the destructor does it. - By unlocking the mutexes before returning, the function can do other - operations without making other threads wait too long. - @return True if unlocked by this, else false if not unlocked by this. - (Which is not the same as whether the mutex itself is locked or not by - another thread.) - */ - bool Unlock( void ); - - /// Returns true if the mutex is locked by this object. - inline bool IsLocked( void ) const { return m_locked; } - - /// Provides access to mutex controlled by this. - const volatile LevelMutexInfo & GetMutex( void ) const { return m_mutex; } - -private: - - /// Default constructor is not implemented. - MutexLocker( void ); - /// Copy constructor is not implemented. - MutexLocker( const MutexLocker & ); - /// Copy-assignment operator is not implemented. - MutexLocker & operator = ( const MutexLocker & ); - - /// True if mutex got locked. - bool m_locked; - - /// Reference to mutex. - volatile LevelMutexInfo & m_mutex; -}; - -// ---------------------------------------------------------------------------- - -/** @class MultiMutexLocker - You can place an instance of this as a local variable inside a function to lock - a collection of mutexes. It locks them if no error occurs, or throws an - exception if one does happen. When the function ends, the destructor determines - if it needs to unlock the mutexes. This RAII technique insures the mutexes get - unlocked even when exceptions occur. You will also have to construct a - MutexContainer as a local object within the same function. - */ -class MultiMutexLocker -{ -public: - - /** Creates an object to lock and unlock a collection of mutexes for a function. - This will throw if an attempt to lock any mutex fails. If an exception occurs, - it unlocks mutexes it previously locked. - @param mutex Reference to a collection of mutexes. - @param lock True if function wants to lock the mutex as this gets - constructed. - */ - explicit MultiMutexLocker( LevelMutexInfo::MutexContainer & mutexes, - bool lock = true ); - - /** Creates an object to lock and unlock a collection of mutexes for a function. - This waits a specified amount of time for other threads to unlock each mutex - that is locked. This will throw if an attempt to lock any mutex fails. If an - exception occurs, it unlocks mutexes it previously locked. - @param mutexes Reference to a collection of mutexes. - @param milliSeconds Amount of time to wait for another thread to unlock - the mutex. - @param lock True if function wants to lock the mutexes as this gets - constructed. - */ - MultiMutexLocker( LevelMutexInfo::MutexContainer & mutexes, - unsigned int milliSeconds, bool lock = true ); - - /// Destructs the locker, and determines if it needs to unlock the mutexes. - ~MultiMutexLocker( void ); - - /** You can call this to lock (or relock) the mutexes. In theory, you can lock - and unlock mutexes several times within a function in order to give other - threads access to resources while this function does not need them. - @return True if mutex is locked by this, else false if not locked. - */ - bool Lock( void ); - - /** You can call this to unlock the mutexes before the destructor does it. - By unlocking the mutexes before returning, the function can do other - operations without making other threads wait too long. - @return True if unlocked by this, else false if not unlocked by this. - (Which is not the same as whether the mutex itself is locked or not by - another thread.) - */ - bool Unlock( void ); - - /// Returns true if the mutexes are locked by this object. - inline bool IsLocked( void ) const { return m_locked; } - - /// Provides access to the collection of mutexes controlled by this. - const LevelMutexInfo::MutexContainer & GetMutexes( void ) const { return m_mutexes; } - -private: - - /// Default constructor is not implemented. - MultiMutexLocker( void ); - /// Copy constructor is not implemented. - MultiMutexLocker( const MultiMutexLocker & ); - /// Copy-assignment operator is not implemented. - MultiMutexLocker & operator = ( const MultiMutexLocker & ); - - /// True if mutexes got locked. - bool m_locked; - - /// Reference to external container of mutexes; - LevelMutexInfo::MutexContainer & m_mutexes; -}; - -// ---------------------------------------------------------------------------- - -}; // end namespace Loki - -#endif // end file guardian +//////////////////////////////////////////////////////////////////////////////// +// +// LevelMutex facility for the Loki Library +// Copyright (c) 2008 Richard Sposato +// The copyright on this file is protected under the terms of the MIT license. +// +// Permission to use, copy, modify, distribute and sell this software for any +// purpose is hereby granted without fee, provided that the above copyright +// notice appear in all copies and that both that copyright notice and this +// permission notice appear in supporting documentation. +// +// The author makes no representations about the suitability of this software +// for any purpose. It is provided "as is" without express or implied warranty. +// +//////////////////////////////////////////////////////////////////////////////// + +// $Id$ + +/// @file LevelMutex.h Defines classes and functions for LevelMutex facility. + +#ifndef LOKI_LEVEL_MUTEX_H_INCLUDED +#define LOKI_LEVEL_MUTEX_H_INCLUDED + + +// ---------------------------------------------------------------------------- + +#include +#include +#include + +#if defined( _MSC_VER ) + #include +#else + #include +#endif + + +/** @par thread_local Keyword + The mutexes require compilers to provide thread local storage - meaning each + thread gets its own copy of the data. The next version of C++ will have a + new keyword, thread_local for that purpose. Some existing compilers already + provide thread local storage using different syntax, so these lines use + thread_local to mimic that syntax. If your compiler provides thread local + storage but using different syntax besides "thread_local", you may want to + modify these lines. If your compiler does not support thread local storage, + you can't use LevelMutex. + */ +#ifndef LOKI_THREAD_LOCAL + #if defined( _MSC_VER ) + #if ( _MSC_VER >= 1300 ) + #define LOKI_THREAD_LOCAL __declspec( thread ) + #else + #error "Only Visual Studio versions 7.0 and after supported." + #endif + + #elif ( __GNUC__ ) + #define LOKI_THREAD_LOCAL __thread + + #else + #warning "Check if your compiler provides thread local storage." + #define LOKI_THREAD_LOCAL thread_local + #endif +#endif + +#if defined( DEBUG ) || defined( _DEBUG ) + #define LOKI_MUTEX_DEBUG_CODE( x ) x +#else + #define LOKI_MUTEX_DEBUG_CODE( x ) +#endif + + +namespace Loki +{ + + +// ---------------------------------------------------------------------------- + +class MutexErrors +{ +public: + + /// @enum Type Possible error conditions detected by LevelMutex functions. + enum Type + { + Success = 0, ///< Operation occurred correctly. + NoProblem, ///< Pre-lock and pre-unlock checks passed. + WrongLevel, ///< All mutexes in container must have same level. + LevelTooLow, ///< Trying to unlock a mutex lower than current level. + LevelTooHigh, ///< Trying to lock a mutex higher than current level. + TryFailed, ///< TryLock call failed to lock mutex. + NullMutexPointer, ///< Container has a NULL pointer in it. + DuplicateMutex, ///< Container must have unique pointers - no duplicates. + EmptyContainer, ///< Container must have at least 1 pointer in it. + AlreadyLocked, ///< TryLock call failed because mutex already locked. + WasntLocked, ///< Unlock failed because mutex was not even locked. + NotRecentLock, ///< Mutex in container was not recently locked by this thread. + NotLockedByThread, ///< Can't unlock a mutex not locked by this thread. + MultiUnlockFailed, ///< MultiUnlock can't unlock at least 1 mutex in container. + TimedOut, ///< Wait time elapsed without locking mutex. + TooMuchRecursion, ///< Tried to relock a PThread mutex which is not re-entrant. + NotInitialized, ///< Tried to lock a PThread mutex which did not get setup. + AlreadyInitialized, ///< PThread mutex initialized before ctor called. + InvalidAttribute, ///< PThread mutex improperly initialized. + InvalidAddress, ///< Bad pointer used to initialize a PThread mutex. + ExceptionThrown, ///< Exception caught in mutex operation. + MayDeadlock, ///< Locking this mutex may cause a deadlock. + OtherError ///< Unknown error occurred. + }; +}; + +// ---------------------------------------------------------------------------- + +/** @class LevelMutexInfo + This monolithic base class stores common info for a template class used to + control mutexes. The template class, LevelMutex, is policy-based class. + + @par Implementation + Each thread has a list of mutexes it locked. When a mutex first gets locked, it + gets added to the head of the list. If locked again, LevelMutex merely increments + a count. When unlocked, the count gets decremented until it reaches zero, and + then it gets removed from the list. Each mutex has a pointer to the mutex most + recently locked by the current thread. The current level of a thread is always + the level of the most recently locked mutex, or UnlockedLevel if the thread does + not have any mutexes locked now. A mutex is considered "recently" locked if it is at + the head of the list, or the same level as the current mutex and also locked by the + current thread. + + @par Class Invariants + This class maintains invariants for each LevelMutexInfo so that no function + calls corrupt a mutex. Each function makes a call to IsValid at the start so + that LevelMutex knows it acts on valid internal data. Many functions call + IsValid again when they return to insure the function did not leave any data in + an invalid state. The exit call to IsValid occurs through a tiny helper class + called Checker to insure all data remain valid even when exceptions occur. + Another helper class, MutexUndoer, unlocks mutexes in a container if an + exception occurs during calls to MultiLock. + + @par Error Results + Many functions return an enum value to indicate an error status. Many enum values + indicate errors detected within LevelMutex, but some indicate errors found in policy + classes, SpinLevelMutex and SleepLevelMutex. + */ + +class LevelMutexInfo +{ +public: + + /** Level for thread that has not locked any mutex. Maximum possible level + for a mutex is UnlockedLevel-1; No mutex may have a level of UnlockedLevel. + */ + static const unsigned int UnlockedLevel = 0xFFFFFFFF; + + /// Container for locking multiple mutexes at once. + typedef ::std::vector< volatile LevelMutexInfo * > MutexContainer; + typedef MutexContainer::iterator LevelMutexContainerIter; + typedef MutexContainer::const_iterator LevelMutexContainerCIter; + typedef MutexContainer::reverse_iterator LevelMutexContainerRIter; + typedef MutexContainer::const_reverse_iterator LevelMutexContainerCRIter; + + /** Locks several mutexes at once. Requires O(m + n*n) actions where m is the + number of mutexes currently locked by the thread and n is the number of mutexes + in the container. This provides strong exception safety. If an exception occurs, + any mutexes that were locked during this call will get unlocked. + @param mutexes Container of pointers to mutexes. Container must have at + least 1 mutex, all mutexes must have the same level, no NULL pointers, and all + mutexes must not exceed the thread's current level. This sorts the container + by address order. + @return Enum value indicating success or error. + */ + static MutexErrors::Type MultiLock( MutexContainer & mutexes ); + + /** Locks several mutexes at once. Requires O(m + n*n + n*t) actions where m is + the number of mutexes currently locked by the thread, n is the number of mutexes + in the container, and t is the wait time for each mutex. This provides strong + exception safety. If an exception occurs, any mutexes that were locked during + this call will ge unlocked. + @param mutexes Container of pointers to mutexes. Container must have at + least 1 mutex, all mutexes must have the same level, no NULL pointers, and all + mutexes must not exceed the thread's current level. This sorts the container + by address order. + @param milliSeconds Amount of time to wait for each mutex. + @return Enum value indicating success or error. + */ + static MutexErrors::Type MultiLock( MutexContainer & mutexes, + unsigned int milliSeconds ); + + /** Unlocks several mutexes at once. Requires O(m) actions where m is the number of + mutexes in the container. This provides strong exception safety. If an exception + occurs when unlocking one mutex, other mutexes in the container get unlocked anyway. + @param mutexes Container of pointers to mutexes. Container must have at least 1 + mutex, all mutexes must have the same level, no NULL pointers, and all mutexes must + be locked by the current thread. This sorts the container dby address order. + @return Enum value indicating success or error. + */ + static MutexErrors::Type MultiUnlock( MutexContainer & mutexes ); + + /** Gives pointer to most recently locked mutex, or NULL if nothing locked. + The pointer is for a const mutex so the mutex can't be modified inappropriately. + The pointer is for a volatile mutex so callers can call volatile member + functions to get info about the mutex. + */ + static const volatile LevelMutexInfo * GetCurrentMutex( void ); + + /// Returns the level of this mutex. + inline unsigned int GetLevel( void ) const volatile { return m_level; } + + /// Returns true if this mutex was locked at least once. + inline bool IsLocked( void ) const volatile { return ( 0 < m_count ); } + + /// Returns count of how many times this mutex got locked. + inline unsigned int GetLockCount( void ) const volatile { return m_count; } + + /// Returns pointer to mutex previously locked by the thread which locked this. + inline const volatile LevelMutexInfo * GetPrevious( void ) const volatile + { + return m_previous; + } + + /** Tries to lock mutex, and returns immediately if mutex already locked by + another thread. It will return immediately with a value of AlreadyLocked + if the mutex was locked by a different thread. It may throw an exception + or assert when errors occur if the ErrorPolicy class implements that behavior. + @return An error condition if any occurred, else Success. + */ + virtual MutexErrors::Type TryLock( void ) volatile = 0; + + /** Blocking call will attempt to lock mutex and wait until it can lock. + This may throw an exception if the lock failed or an error occurred - if + that is what the error policy specifies. + @return An error condition if any occurred, else Success. + */ + virtual MutexErrors::Type Lock( void ) volatile = 0; + + /** Attempts to lock mutex, but only waits for a limited amount of time + before it gives up. Will return quickly if an error occurs before any + attempt to lock. This may throw an exception if the lock failed or an + error occurred - if that is what the error policy specifies. + @param milliSeconds How long to wait. + @return An error condition if any occurred, else Success. + */ + virtual MutexErrors::Type Lock( unsigned int milliSeconds ) volatile = 0; + + /** Unlocks the mutex, or returns an error condition. This may throw an + exception if the lock failed or an error occurred - if that is what the + error policy specifies. + @return An error condition if any occurred, else Success. + */ + virtual MutexErrors::Type Unlock( void ) volatile = 0; + + /** Returns true if this mutex was locked by current thread, and level is the same + as the current thread's level. Which means this was the most recently locked + mutex, or it was locked along with several others of the same level recently. + */ + bool IsRecentLock( void ) const volatile; + + /** Returns true if this mutex was locked within the last count mutexes. + @param count How many recent mutexes to look through to find this mutex. + */ + bool IsRecentLock( unsigned int count ) const volatile; + + /// Returns true if this was locked by current thread. + bool IsLockedByCurrentThread( void ) const volatile; + + /// Returns true if this was locked by another thread. + bool IsLockedByAnotherThread( void ) const volatile; + +protected: + + /** @class Checker Performs validity check on mutex to insure no class invariants + were violated inside any member function. This class only gets used in debug + builds, and any instance of it gets optimized away in release builds. A checker + is created inside many of member functions so that it's destructor gets called + when the function exits. It determines if any class invariants were violated + during the function call. + */ + class Checker + { + public: + inline explicit Checker( const volatile LevelMutexInfo * mutex ) : + m_mutex( mutex ) {} + inline ~Checker( void ) { m_mutex->IsValid(); } + private: + Checker( void ); + Checker( const Checker & ); + Checker & operator = ( const Checker & ); + const volatile LevelMutexInfo * m_mutex; + }; + + /** @class MutexUndoer + Undoes actions by MultiLock if an exception occurs. It keeps track of + which mutexes in a container got locked, and if an exception occurs, then + the destructor unlocks them. If MultiLock succeeds, then it cancels the + undoer so nothing gets unlocked inadvertently. + */ + class MutexUndoer + { + public: + + explicit MutexUndoer( MutexContainer & mutexes ); + ~MutexUndoer( void ); + void SetPlace( LevelMutexContainerIter & here ); + void Cancel( void ); + + private: + + MutexUndoer( void ); + MutexUndoer( const MutexUndoer & ); + MutexUndoer & operator = ( const MutexUndoer & ); + + MutexContainer & m_mutexes; + LevelMutexContainerIter m_here; + }; + + /** Returns true if linked-list of locked mutexes in this thread is valid. + Which means the list has no loops, and each previous mutex on the list has a + higher or same level as the current mutex. Called by IsValid. + */ + static bool IsValidList( void ); + + /** This is the only available constructor, and it forces any derived class to set + a level for each mutex. + */ + explicit LevelMutexInfo( unsigned int level ); + + /// The destructor only gets called by the derived class. + virtual ~LevelMutexInfo( void ); + + MutexErrors::Type PreLockCheck( bool forTryLock ) volatile; + + MutexErrors::Type PreUnlockCheck( void ) volatile; + + /** This gets called after each call to DoLock and DoTryLock to make sure the data + members in this object get set correctly. + */ + void PostLock( void ) volatile; + + /// Gets called just before an attempt to unlock a mutex. + void PreUnlock( void ) volatile; + + /// Called to relock a mutex already locked by the current thread. + void IncrementCount( void ) volatile; + + /// Called to unlock a mutex locked multiple times by the current thread. + void DecrementCount( void ) volatile; + + /** Returns true if no class invariant broken, otherwise asserts. This function + only gets called in debug builds. + */ + bool IsValid( void ) const volatile; + +private: + + /// Copy constructor is not implemented. + LevelMutexInfo( const LevelMutexInfo & ); + /// Copy-assignment operator is not implemented. + LevelMutexInfo & operator = ( const LevelMutexInfo & ); + + /** Called only by MultiLock & MultiUnlock to pass a result through an + error checking policy. + @param result What error condition to check. + @return Result or assertion or an exception - depending on error policy. + */ + virtual MutexErrors::Type DoErrorCheck( MutexErrors::Type result ) const volatile = 0; + + /// Called only by MultiLock to Lock each particular mutex within a container. + virtual MutexErrors::Type LockThis( void ) volatile = 0; + + /** Called only by MultiLock to lock each particular mutex within a container. + @param milliSeconds How much time to wait before giving up on locking a mutex. + */ + virtual MutexErrors::Type LockThis( unsigned int milliSeconds ) volatile = 0; + + /// Called only by MultiUnlock to unlock each particular mutex within a container. + virtual MutexErrors::Type UnlockThis( void ) volatile = 0; + + /// Pointer to singly-linked list of mutexes locked by the current thread. + static LOKI_THREAD_LOCAL volatile LevelMutexInfo * s_currentMutex; + + /// Level of this mutex. + const unsigned int m_level; + + /// How many times this mutex got locked. + unsigned int m_count; + + /// Pointer to mutex locked before this one. + volatile LevelMutexInfo * m_previous; + +}; + +// ---------------------------------------------------------------------------- + +/** @class ThrowOnAnyMutexError + Implements the ErrorPolicy for LevelMutex and throws an exception for any + error condition. Only allows MutexErrors::Success and MutexErrors::NoProblem + to get through. Useful for release builds. + */ +class ThrowOnAnyMutexError +{ +public: + static MutexErrors::Type CheckError( MutexErrors::Type error, + unsigned int level ); +}; + +// ---------------------------------------------------------------------------- + +/** @class ThrowOnBadDesignMutexError + Implements the ErrorPolicy for LevelMutex and throws an exception if the error + indicates the programmer did not levelize the calls to mutexes. Otherwise + returns the error result. Useful for release builds. + */ +class ThrowOnBadDesignMutexError +{ +public: + static MutexErrors::Type CheckError( MutexErrors::Type error, + unsigned int level ); +}; + +// ---------------------------------------------------------------------------- + +/** @class AssertAnyMutexError + Implements the ErrorPolicy for LevelMutex and asserts for any error condition. + Only allows MutexErrors::Success and MutexErrors::NoProblem to get through. + Useful for testing mutexes in debug builds. + */ +class AssertAnyMutexError +{ +public: + static inline MutexErrors::Type CheckError( MutexErrors::Type error, + unsigned int level ) + { + (void)level; + assert( ( error == MutexErrors::Success ) + || ( error == MutexErrors::NoProblem ) ); + return error; + } +}; + +// ---------------------------------------------------------------------------- + +/** @class AssertBadDesignMutexError + Implements the ErrorPolicy for LevelMutex and asserts if the error + indicates the programmer did not levelize the calls to mutexes. Otherwise + returns the error result. Useful for testing mutexes in debug builds. + */ +class AssertBadDesignMutexError +{ +public: + static inline MutexErrors::Type CheckError( MutexErrors::Type error, + unsigned int level ) + { + (void)level; + assert( ( error != MutexErrors::LevelTooHigh ) + && ( error != MutexErrors::LevelTooLow ) ); + return error; + } +}; + +// ---------------------------------------------------------------------------- + +/** @class JustReturnMutexError + Implements the ErrorPolicy for LevelMutex and does nothing no matter how bad + the error condition. Only recommended use is for automated unit-testing of + mutex policies. + */ +class JustReturnMutexError +{ +public: + static inline MutexErrors::Type CheckError( MutexErrors::Type error, + unsigned int level ) + { + (void)level; + return error; + } +}; + +// ---------------------------------------------------------------------------- + +/** @class NoMutexWait + Implements the WaitPolicy for LevelMutex. Does nothing at all so it turns + all wait loops into spin loops. Useful for low-level mutexes. + */ +class NoMutexWait +{ +public: + static inline void Wait( void ) {} +}; + +// ---------------------------------------------------------------------------- + +/** @class MutexSleepWaits + Implements the WaitPolicy for LevelMutex. Sleeps for a moment so thread won't + consume idle CPU cycles. Useful for high-level mutexes. + */ +class MutexSleepWaits +{ +public: + static void Wait( void ); + static unsigned int sleepTime; +}; + +// ---------------------------------------------------------------------------- + +/** @class SpinLevelMutex + Implements a spin-loop to wait for the mutex to unlock. Since this class makes + the thread wait in a tight spin-loop, it can cause the thread to remain busy + while waiting and thus consume CPU cycles. For that reason, this mutex is best + used only for very low-level resources - especially resources which do not + require much CPU time to exercise. Rule of thumb: Use this only if all actions + on the resource consume a very small number of CPU cycles. Otherwise, use the + SleepLevelMutex instead. + */ +class SpinLevelMutex +{ +public: + + /// Constructs a spin-level mutex. + explicit SpinLevelMutex( unsigned int level ); + + /// Destructs the mutex. + virtual ~SpinLevelMutex( void ); + + virtual MutexErrors::Type Lock( void ) volatile; + + virtual MutexErrors::Type TryLock( void ) volatile; + + virtual MutexErrors::Type Unlock( void ) volatile; + + inline unsigned int GetLevel( void ) const volatile { return m_level; } + +private: + + /// Copy constructor is not implemented. + SpinLevelMutex( const SpinLevelMutex & ); + /// Copy-assignment operator is not implemented. + SpinLevelMutex & operator = ( const SpinLevelMutex & ); + +#if defined( _MSC_VER ) + #if ( _MSC_VER >= 1300 ) + /// The actual mutex. + CRITICAL_SECTION m_mutex; + #else + #error "Only Visual Studio versions 7.0 and after supported." + #endif + +#elif ( __GNUC__ ) + /// The actual mutex. + pthread_mutex_t m_mutex; + +#else + #error "Check if any mutex libraries are compatible with your compiler." +#endif + + /// Keep a copy of the mutex level around for error reporting. + const unsigned int m_level; + +}; // end class SpinLevelMutex + +// ---------------------------------------------------------------------------- + +/** @class SleepLevelMutex + Implements a sleeping loop to wait for the mutex to unlock. + + @par Purpose + Since this class puts the thread to sleep for short intervals, you can use this + class for most of your mutexes. Especially for locking any high level resources + where any one operation on the resouce consumes many CPU cycles. The purpose of + this mutex is to reduce the number of CPU cycles spent in idle loops. All + SleepLevelMutex's should have higher levels than all your SpinLevelMutex's. + + @par Dependence on SpinLevelMutex + This utilizes SpinLevelMutex so it does not have to re-implement the DoTryLock + and DoUnlock functions the same way. All it really needs is a DoLock function + and the amount of time it should sleep if an attempt to lock a function fails. + */ +class SleepLevelMutex : public SpinLevelMutex +{ +public: + + /** Constructs a levelized mutex that puts threads to sleep while they wait + for another thread to unlock the mutex. + @param level Level of this mutex. + */ + explicit SleepLevelMutex( unsigned int level ); + + SleepLevelMutex( unsigned int level, unsigned int sleepTime ); + + /// Destructs the mutex. + virtual ~SleepLevelMutex( void ); + + inline unsigned int GetSleepTime( void ) const volatile { return m_sleepTime; } + + inline void SetSleepTime( unsigned int sleepTime ) volatile + { + if ( 0 != sleepTime ) + m_sleepTime = sleepTime; + } + +#if defined( _MSC_VER ) + inline bool GetWakable( void ) const volatile { return m_wakable; } + inline void SetWakable( bool wakable ) volatile { m_wakable = wakable; } +#endif + + /** Attempts to lock a mutex, and if it fails, then sleeps for a while + before attempting again. + */ + virtual MutexErrors::Type Lock( void ) volatile; + +private: + + /// Default constructor is not implemented. + SleepLevelMutex( void ); + /// Copy constructor is not implemented. + SleepLevelMutex( const SleepLevelMutex & ); + /// Copy-assignment operator is not implemented. + SleepLevelMutex & operator = ( const SleepLevelMutex & ); + +#if defined( _MSC_VER ) + #if ( _MSC_VER >= 1300 ) + /// True if operating system may wake thread to respond to events. + bool m_wakable; + #else + #error "Only Visual Studio versions 7.0 and after supported." + #endif +#endif + + /// How many milli-seconds to sleep before trying to lock mutex again. + unsigned int m_sleepTime; + +}; // end class SleepLevelMutex + +// ---------------------------------------------------------------------------- + +/** @class LevelMutex + Levelized mutex class prevents deadlocks by requiring programs to lock mutexes in + the same order, and unlock them in reverse order. This is accomplished by forcing + each mutex to have a level and forcing code to lock mutexes with higher levels + before locking mutexes at lower levels. If you want to lock several mutexes, they + must be locked in decreasing order by level, or if they are all of the same level, + then locked by LevelMutex::MultiLock. + + @par Features + - Immune: Very unlikely to deadlock since all mutexes are locked in the same + order and unlocked in reverse order. + - Scalable: Can handle any number of mutexes. + - Efficient: Many operations occur in constant time, and most operations require + no more than O(m) steps. + - Exception safe: All operations provide strong safety or don't throw. + - Extendable: Can work with existing mutexes through policy-based design. + - Easily Extended: Derived classes only need to implement 5 functions and a mutex + to get all the features of this class. + - Re-Entrant: Allows for re-entrancy even if mutexes in policy classes don't. + - Cost-Free: No resource allocations occur in LevelMutex - although user-defined + policy classes may allocate resources. + - Compact: Each LevelMutex object is small. + - Portable: As long as your compiler and libraries can meet the requirements. + - Robust: Maintains data integrity even if exceptions occur in policy classes. + - Affording: Several functions provide information about a mutex which allows + client code to easily choose correct actions. + + @par Requirements + - Your compiler must allow for thread-specific data. + - You must have a threading or mutex library. + + @par Policy-Based Design + This class hosts 3 policies and a default level. The policy-based design allows + users to write their own policies to extend the behaviors of LevelMutex. The + paragraphs below say how to design a class for each policy. + - MutexPolicy The mutex policy class. + - defaultLevel A level for existing client code that calls a default constructor. + - ErrorPolicy How the mutex should handle error conditions. + - WaitPolicy Whether a thread should wait, and how long in some internal loops. + + @par MutexPolicy + A policy class that wraps a low-level mutex. Loki provides two policy classes + for the actual mutex (SpinLevelMutex and SleepLevelMutex), both of which wrap + either pthreads or the Windows CRITICAL_SECTION. If you want to use a mutex + mechanism besides one of those, then all you have to do is provide a class + which wraps the mutex and implements these functions. + explicit SpinLevelMutex( unsigned int level ); + virtual ~SpinLevelMutex( void ); + virtual MutexErrors::Type Lock( void ) volatile; + virtual MutexErrors::Type TryLock( void ) volatile; + virtual MutexErrors::Type Unlock( void ) volatile; + Indeed, since the base class does most of the work, and provides all the interace + and functionality to client classes, a derived class has very few requirements. + It only needs to implement a single constructor, the destructor, some virtual + functions, and whatever data members it requires. You don't actually need to + declare those functions as virtual if the policy class is not a base or child + class. In the parlance of design patterns, LevelMutex is a Template, and the + MutexPolicy is a Strategy. + + @par DefaultLevel + The template class requires a default level to use inside the default constructor. + Some existing code calls instantiates mutexes with a default constructor, so the + mutex must know what level to use there. Please do not use zero or UnlockedLevel + as the default level. + + @par ErrorPolicy + This policy specifies how to handle error conditions. The mutexes can return + errors, assert, or throw exceptions. I recommend that debug code use asserts, + release code use exceptions, and unit-testing code just return errors. The + error policy class only needs to implement one function: + static MutexErrors::Type CheckError( MutexErrors::Type error, unsigned int level ); + + @par WaitPolicy + This states whether the mutex should wait within some tight internal loops, + how the waiting is done, and for how long. A wait policy class could sleep, + do nothing, check if other objects need attention, or check if the program + received events or notices from the operating system. It only needs to + implement one function: + static void Wait( void ); + + @par Per-Function Usage + If you implement a function with a static local mutex, then you have to insure + the function is not called from a lower level via call-backs, virtual functions in + interface classes. If the function does get called from a lower level, you are + setting up a potential deadlock. LevelMutex will detect that by checking the + current level and the local mutex's level, so it will refuse to lock the local mutex. + + @par Per-Object Usage + If you use a mutex as a data member of an object to protect that object, then I + recommend specifying which functions are volatile and which are not, and then only + use the mutex within the volatile functions. You may also want to provide accessor + functions so that client code can lock and unlock the mutex either to allow for + calling multiple operations without having to lock and unlock before and after each + operation, or so they can lock it along with several other objects at the same + level. + + @par Per-Class Usage + If you make a static data member within a class, you can use that to lock any + resources shared by those objects, or to require threads to act on only one object + at a time. You may also want to provide static accessor functions so that client + code can lock several other resources at the same level. + */ + +template +< + class MutexPolicy, + unsigned int DefaultLevel, + class ErrorPolicy = ::Loki::ThrowOnBadDesignMutexError, + class WaitPolicy = ::Loki::NoMutexWait +> +class LevelMutex : public LevelMutexInfo +{ +public: + + typedef ErrorPolicy EP; + typedef WaitPolicy WP; + typedef MutexPolicy MP; + + /** This constructor allows callers to replace the default level with another + value. It also acts as the default constructor for existing code which uses + default construction for mutexes. This is the only time the DefaultLevel + template parameter gets used. + */ + explicit LevelMutex( unsigned int level = DefaultLevel ) : + LevelMutexInfo( level ), + m_mutex( level ) + { + assert( IsValid() ); + } + + /// The destructor. + ~LevelMutex( void ) + { + assert( IsValid() ); + } + + /** These functions allow callers to access the mutex in case they need to + modify specific values in the MutexPolicy (e.g. - sleep time, functors to + call as tasks, etc...) There is one function for every combination of + const and volatile qualifiers so callers get a reference to a MutexPolicy + with the proper qualifiers. + */ + inline const volatile MutexPolicy & GetMutexPolicy( void ) const volatile { return m_mutex; } + inline volatile MutexPolicy & GetMutexPolicy( void ) volatile { return m_mutex; } + inline const MutexPolicy & GetMutexPolicy( void ) const { return m_mutex; } + inline MutexPolicy & GetMutexPolicy( void ) { return m_mutex; } + + virtual MutexErrors::Type TryLock( void ) volatile + { + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + MutexErrors::Type result = LevelMutexInfo::PreLockCheck( true ); + if ( MutexErrors::Success == result ) + return MutexErrors::Success; + else if ( MutexErrors::AlreadyLocked == result ) + return result; + else if ( MutexErrors::NoProblem != result ) + return EP::CheckError( result, GetLevel() ); + + assert( 0 == LevelMutexInfo::GetLockCount() ); + result = m_mutex.TryLock(); + if ( MutexErrors::Success != result ) + return EP::CheckError( result, GetLevel() ); + LevelMutexInfo::PostLock(); + + return MutexErrors::Success; + } + + virtual MutexErrors::Type Lock( void ) volatile + { + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + MutexErrors::Type result = LevelMutexInfo::PreLockCheck( false ); + if ( MutexErrors::Success == result ) + return MutexErrors::Success; + else if ( MutexErrors::NoProblem != result ) + return EP::CheckError( result, GetLevel() ); + + assert( !LevelMutexInfo::IsLockedByCurrentThread() ); + result = m_mutex.Lock(); + if ( MutexErrors::Success != result ) + return EP::CheckError( result, GetLevel() ); + PostLock(); + + return MutexErrors::Success; + } + + virtual MutexErrors::Type Lock( unsigned int milliSeconds ) volatile + { + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + MutexErrors::Type result = LevelMutexInfo::PreLockCheck( false ); + if ( MutexErrors::Success == result ) + return MutexErrors::Success; + else if ( MutexErrors::NoProblem != result ) + return EP::CheckError( result, GetLevel() ); + + assert( !LevelMutexInfo::IsLockedByCurrentThread() ); + clock_t timeOut = clock() + milliSeconds; + while ( clock() < timeOut ) + { + WP::Wait(); + result = m_mutex.TryLock(); + switch ( result ) + { + case MutexErrors::Success: + { + PostLock(); + return MutexErrors::Success; + } + case MutexErrors::AlreadyLocked: + return MutexErrors::AlreadyLocked; + case MutexErrors::TryFailed: + break; + default: + return EP::CheckError( result, GetLevel() ); + } + } + + return MutexErrors::TimedOut; + } + + virtual MutexErrors::Type Unlock( void ) volatile + { + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + MutexErrors::Type result = LevelMutexInfo::PreUnlockCheck(); + if ( MutexErrors::Success == result ) + return MutexErrors::Success; + else if ( MutexErrors::NoProblem != result ) + return EP::CheckError( result, GetLevel() ); + + LevelMutexInfo::PreUnlock(); + result = MutexErrors::OtherError; + try + { + result = m_mutex.Unlock(); + if ( MutexErrors::Success != result ) + PostLock(); + } + catch ( ... ) + { + PostLock(); + result = MutexErrors::ExceptionThrown; + } + + return result; + } + +private: + + /// Copy constructor is not implemented since mutexes don't get copied. + LevelMutex( const LevelMutex & ); + /// Copy-assignment operator is not implemented since mutexes don't get copied. + LevelMutex & operator = ( const LevelMutex & ); + + virtual MutexErrors::Type DoErrorCheck( MutexErrors::Type result ) const volatile + { + return EP::CheckError( result, GetLevel() ); + } + + /** Called only by MultiLock to lock each particular mutex within a container. + This does not do pre-lock error checking since MultiLock does that. Since + this skips the error checking, that means that callers of LevelMutex should + not call this function directly, and so this will not be publicly available. + @return Error status indicating success or reason for failure. + */ + virtual MutexErrors::Type LockThis( void ) volatile + { + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + assert( this != LevelMutexInfo::GetCurrentMutex() ); + + const MutexErrors::Type result = m_mutex.Lock(); + if ( MutexErrors::Success != result ) + return result; + PostLock(); + + return MutexErrors::Success; + } + + /** Called only by MultiLock to lock each particular mutex within a container. + This does not do pre-lock error checking since MultiLock does that. Since + this skips the error checking, callers of LevelMutex should not call this + function directly, and so this will not be publicly available. + @param milliSeconds How much time to wait before giving up on locking a mutex. + @return Error status indicating success or reason for failure. + */ + virtual MutexErrors::Type LockThis( unsigned int milliSeconds ) volatile + { + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + clock_t timeOut = clock() + milliSeconds; + while ( clock() < timeOut ) + { + WP::Wait(); + const bool locked = ( MutexErrors::Success == m_mutex.TryLock() ); + if ( locked ) + { + PostLock(); + return MutexErrors::Success; + } + } + + return MutexErrors::TimedOut; + } + + /** Called only by MultiUnlock to unlock each mutex within a container. + This does not do pre-unlock error checking since MultiLock does that. Since + this skips the error checking, callers of LevelMutex should not call this + function directly, and so this will not be publicly available. + @return Error status indicating success or reason for failure. + */ + virtual MutexErrors::Type UnlockThis( void ) volatile + { + assert( IsValid() ); + assert( NULL != LevelMutexInfo::GetCurrentMutex() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + if ( 1 < LevelMutexInfo::GetLockCount() ) + { + LevelMutexInfo::DecrementCount(); + return MutexErrors::Success; + } + + LevelMutexInfo::PreUnlock(); + MutexErrors::Type result = m_mutex.Unlock(); + + return result; + } + + /// An instance of an unleveled mutex wrapped to match LevelMutex's needs. + MutexPolicy m_mutex; + +}; // end class LevelMutex + +// ---------------------------------------------------------------------------- + +/** Returns level of most recently locked mutex by this thread, or UnlockedLevel + if no mutexes are locked. Runs in constant time, and never throws exceptions. + */ +unsigned int GetCurrentThreadsLevel( void ); + +/** Returns count of how mutexes the current thread locked. Requires O(m) + actions where m is the number of mutexes in the thread. Never throws exceptions. + */ +unsigned int CountMutexesInCurrentThread( void ); + +/** Returns count of how mutexes the current thread locked. The lock count + exceeds the number of mutexes locked by current thread if any mutex got locked + more than once. Requires O(m) actions where m is the number of mutexes in the + thread. Never throws exceptions. + */ +unsigned int CountLocksInCurrentThread( void ); + +/** Returns count of mutexes locked by current thread which have the same level + as GetCurrentThreadsLevel. Requires O(m) actions where m is the number of + mutexes in the thread at current level. Never throws exceptions. + */ +unsigned int CountMutexesAtCurrentLevel( void ); + +/** Determines if container of mutexes matches the recently locked mutexes. + If they do match, it returns success, otherwise an error condition. + */ +MutexErrors::Type DoMutexesMatchContainer( const LevelMutexInfo::MutexContainer & mutexes ); + +// ---------------------------------------------------------------------------- + +/** @class MutexException + Exception class used to throw error statuses about LevelMutex's up to code that + can respond to mutex problems. This class exists because it conveys more info + about the error condition than just ::std::exception. + */ +class MutexException : public ::std::exception +{ +public: + + /** Constructs an exception which stores information about a mutex and the + reason an attempt to use a mutex failed. + */ + MutexException( const char * message, unsigned int level, MutexErrors::Type reason ); + + /// Copy constructor performs a member-by-member copy of an exception. + MutexException( const MutexException & that ) throw (); + + /// Copy-assignment operator performs a member-by-member copy of an exception. + MutexException & operator = ( const MutexException & that ) throw (); + + /// Destroys the exception. + virtual ~MutexException( void ) throw(); + + /// Returns a simple message about which operation failed. + virtual const char * what( void ) const throw(); + + /// Returns level of mutex(es) used when problem occurred. + unsigned int GetLevel( void ) const { return m_level; } + + /// Returns an error status for why operation failed. + MutexErrors::Type GetReason( void ) const { return m_reason; } + +private: + + /// Default constructor is not implemented. + MutexException( void ) throw (); + + /// Simple message about operation that failed. + const char * m_message; + /// Level of mutex(es) used when problem occurred. + unsigned int m_level; + /// Error status for why operation failed. + MutexErrors::Type m_reason; + +}; // end class MutexException + +// ---------------------------------------------------------------------------- + +/** @class MutexLocker + You can place an instance of this as a local variable inside a function to lock + a single mutex. It will lock the mutex if no error occurs, or throw if one + does happen. When the function ends, the destructor will determine if it needs + to unlock the mutex. This RAII technique insures the mutex gets unlocked even + when exceptions occur. + */ +class MutexLocker +{ +public: + + /** Creates an object to lock an unlock a mutex for a function. This + will throw if an attempt to lock the mutex fails. + @param mutex Reference to the mutex. + @param lock True if function wants to lock the mutex as this gets + constructed. + */ + explicit MutexLocker( volatile LevelMutexInfo & mutex, bool lock = true ); + + /** Creates an object to lock an unlock a mutex for a function. This waits + a specified amount of time for another thread to unlock the mutex if it is + locked. This will throw if an attempt to lock the mutex fails. + @param mutex Reference to the mutex. + @param milliSeconds Amount of time to wait for another thread to unlock + the mutex. + @param lock True if function wants to lock the mutex as this gets + constructed. + */ + MutexLocker( volatile LevelMutexInfo & mutex, unsigned int milliSeconds, + bool lock = true ); + + /// Destructs the locker, and determines if it needs to unlock the mutex. + ~MutexLocker( void ); + + /** You can call this to lock (or relock) a mutex. In theory, you can lock + and unlock a mutex several times within a function in order to give other + threads access to a resource while this function does not need it. + @return True if mutex is locked by this, else false if not locked. + */ + bool Lock( void ); + + /** You can call this to unlock a mutex before the destructor does it. + By unlocking the mutexes before returning, the function can do other + operations without making other threads wait too long. + @return True if unlocked by this, else false if not unlocked by this. + (Which is not the same as whether the mutex itself is locked or not by + another thread.) + */ + bool Unlock( void ); + + /// Returns true if the mutex is locked by this object. + inline bool IsLocked( void ) const { return m_locked; } + + /// Provides access to mutex controlled by this. + const volatile LevelMutexInfo & GetMutex( void ) const { return m_mutex; } + +private: + + /// Default constructor is not implemented. + MutexLocker( void ); + /// Copy constructor is not implemented. + MutexLocker( const MutexLocker & ); + /// Copy-assignment operator is not implemented. + MutexLocker & operator = ( const MutexLocker & ); + + /// True if mutex got locked. + bool m_locked; + + /// Reference to mutex. + volatile LevelMutexInfo & m_mutex; +}; + +// ---------------------------------------------------------------------------- + +/** @class MultiMutexLocker + You can place an instance of this as a local variable inside a function to lock + a collection of mutexes. It locks them if no error occurs, or throws an + exception if one does happen. When the function ends, the destructor determines + if it needs to unlock the mutexes. This RAII technique insures the mutexes get + unlocked even when exceptions occur. You will also have to construct a + MutexContainer as a local object within the same function. + */ +class MultiMutexLocker +{ +public: + + /** Creates an object to lock and unlock a collection of mutexes for a function. + This will throw if an attempt to lock any mutex fails. If an exception occurs, + it unlocks mutexes it previously locked. + @param mutex Reference to a collection of mutexes. + @param lock True if function wants to lock the mutex as this gets + constructed. + */ + explicit MultiMutexLocker( LevelMutexInfo::MutexContainer & mutexes, + bool lock = true ); + + /** Creates an object to lock and unlock a collection of mutexes for a function. + This waits a specified amount of time for other threads to unlock each mutex + that is locked. This will throw if an attempt to lock any mutex fails. If an + exception occurs, it unlocks mutexes it previously locked. + @param mutexes Reference to a collection of mutexes. + @param milliSeconds Amount of time to wait for another thread to unlock + the mutex. + @param lock True if function wants to lock the mutexes as this gets + constructed. + */ + MultiMutexLocker( LevelMutexInfo::MutexContainer & mutexes, + unsigned int milliSeconds, bool lock = true ); + + /// Destructs the locker, and determines if it needs to unlock the mutexes. + ~MultiMutexLocker( void ); + + /** You can call this to lock (or relock) the mutexes. In theory, you can lock + and unlock mutexes several times within a function in order to give other + threads access to resources while this function does not need them. + @return True if mutex is locked by this, else false if not locked. + */ + bool Lock( void ); + + /** You can call this to unlock the mutexes before the destructor does it. + By unlocking the mutexes before returning, the function can do other + operations without making other threads wait too long. + @return True if unlocked by this, else false if not unlocked by this. + (Which is not the same as whether the mutex itself is locked or not by + another thread.) + */ + bool Unlock( void ); + + /// Returns true if the mutexes are locked by this object. + inline bool IsLocked( void ) const { return m_locked; } + + /// Provides access to the collection of mutexes controlled by this. + const LevelMutexInfo::MutexContainer & GetMutexes( void ) const { return m_mutexes; } + +private: + + /// Default constructor is not implemented. + MultiMutexLocker( void ); + /// Copy constructor is not implemented. + MultiMutexLocker( const MultiMutexLocker & ); + /// Copy-assignment operator is not implemented. + MultiMutexLocker & operator = ( const MultiMutexLocker & ); + + /// True if mutexes got locked. + bool m_locked; + + /// Reference to external container of mutexes; + LevelMutexInfo::MutexContainer & m_mutexes; +}; + +// ---------------------------------------------------------------------------- + +} // end namespace Loki + +#endif // end file guardian diff --git a/src/LevelMutex.cpp b/src/LevelMutex.cpp index a8e831a..b2290d4 100644 --- a/src/LevelMutex.cpp +++ b/src/LevelMutex.cpp @@ -1,1148 +1,1149 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// LevelMutex facility for the Loki Library -// Copyright (c) 2008 Richard Sposato -// The copyright on this file is protected under the terms of the MIT license. -// -// Permission to use, copy, modify, distribute and sell this software for any -// purpose is hereby granted without fee, provided that the above copyright -// notice appear in all copies and that both that copyright notice and this -// permission notice appear in supporting documentation. -// -// The author makes no representations about the suitability of this software -// for any purpose. It is provided "as is" without express or implied warranty. -// -//////////////////////////////////////////////////////////////////////////////// - -// $Id$ - -/// @file LevelMutex.cpp Contains functions needed by LevelMutex class. - -// ---------------------------------------------------------------------------- - -#include - -#include - - -using namespace ::std; - -// define nullptr even though new compilers will have this keyword just so we -// have a consistent and easy way of identifying which uses of 0 mean null. -#define nullptr 0 - - -volatile ::Loki::LevelMutexInfo * ::Loki::LevelMutexInfo::s_currentMutex = nullptr; - -unsigned int ::Loki::MutexSleepWaits::sleepTime = 1; - - -/// Anonymous namespace hides some functions which are implementation details. -namespace -{ - -// ---------------------------------------------------------------------------- - -/** Determines if the mutex at specific iterator location is unique within the -container of mutexes. It only checks mutexes at later locations in the -container instead of the entire container partly for efficiency sake. (Any -prior duplications would have gotten caught during earlier calls to this -function.) This should not throw exceptions. It requires O(m) operations -where m is the number of elements in the container after the iterator. - @param mutexes Container to check. - @param cit Location of mutex used for comparing. - @return True for uniqueness, false if a duplicate exists. - */ -bool IsUniqueMutex( const ::Loki::LevelMutexInfo::MutexContainer & mutexes, - ::Loki::LevelMutexInfo::LevelMutexContainerCIter cit ) -{ - assert( mutexes.end() != cit ); - - const ::Loki::LevelMutexInfo::LevelMutexContainerCIter end = mutexes.end(); - const volatile ::Loki::LevelMutexInfo * mutex = *cit; - for ( ++cit; cit != end; ++cit ) - { - const volatile ::Loki::LevelMutexInfo * check = *cit; - if ( check == mutex ) - return false; - } - return true; -} - -// ---------------------------------------------------------------------------- - -/** Returns pointer to first mutex it finds in the container. This should not - throw, and takes O(1) most of the time. At worse, it takes O(m) operations - where m is the size of the container. - @param mutexes Container of mutexes. - @return Pointer to first mutex it finds, or nullptr if container is empty or - each element is a nullptr. - */ -const volatile ::Loki::LevelMutexInfo * GetFirstMutex( - const ::Loki::LevelMutexInfo::MutexContainer & mutexes ) -{ - if ( mutexes.size() == 0 ) - return nullptr; - ::Loki::LevelMutexInfo::LevelMutexContainerCIter it( mutexes.begin() ); - const volatile ::Loki::LevelMutexInfo * mutex = *it; - if ( nullptr != mutex ) - return mutex; - - const ::Loki::LevelMutexInfo::LevelMutexContainerCIter end( mutexes.end() ); - while ( it != end ) - { - mutex = *it; - if ( nullptr != mutex ) - return mutex; - ++it; - } - - return nullptr; -} - -// ---------------------------------------------------------------------------- - -/** Gets the level number associated with the first mutex found in a container. - Usually takes O(1) operations, but take up to O(m) where m is the size of the - container. - @return Level number of first mutex in container, or UnlockedLevel if no - mutexes were found in the container. - */ -unsigned int GetLevel( const ::Loki::LevelMutexInfo::MutexContainer & mutexes ) -{ - const volatile ::Loki::LevelMutexInfo * mutex = GetFirstMutex( mutexes ); - return ( nullptr == mutex ) ? ::Loki::LevelMutexInfo::UnlockedLevel : mutex->GetLevel(); -} - -// ---------------------------------------------------------------------------- - -}; // end anonymous namespace - -namespace Loki -{ - -// ---------------------------------------------------------------------------- - -unsigned int GetCurrentThreadsLevel( void ) -{ - const volatile LevelMutexInfo * mutex = LevelMutexInfo::GetCurrentMutex(); - return ( nullptr == mutex ) ? LevelMutexInfo::UnlockedLevel : mutex->GetLevel(); -} - -// ---------------------------------------------------------------------------- - -unsigned int CountMutexesInCurrentThread( void ) -{ - const volatile LevelMutexInfo * mutex = LevelMutexInfo::GetCurrentMutex(); - unsigned int count = 0; - while ( nullptr != mutex ) - { - count++; - mutex = mutex->GetPrevious(); - } - return count; -} - -// ---------------------------------------------------------------------------- - -unsigned int CountLocksInCurrentThread( void ) -{ - const volatile LevelMutexInfo * mutex = LevelMutexInfo::GetCurrentMutex(); - unsigned int count = 0; - while ( nullptr != mutex ) - { - count += mutex->GetLockCount(); - mutex = mutex->GetPrevious(); - } - return count; -} - -// ---------------------------------------------------------------------------- - -unsigned int CountMutexesAtCurrentLevel( void ) -{ - const volatile LevelMutexInfo * mutex = LevelMutexInfo::GetCurrentMutex(); - if ( nullptr == mutex ) - return 0; - unsigned int count = 0; - unsigned int level = mutex->GetLevel(); - while ( nullptr != mutex ) - { - if ( level != mutex->GetLevel() ) - break; - mutex = mutex->GetPrevious(); - count++; - } - return count; -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type DoMutexesMatchContainer( const LevelMutexInfo::MutexContainer & mutexes ) -{ - const unsigned int count = mutexes.size(); - if ( 0 == count ) - return MutexErrors::EmptyContainer; - unsigned int currentLevel = GetCurrentThreadsLevel(); - const LevelMutexInfo::LevelMutexContainerCIter endSpot = mutexes.end(); - - for ( LevelMutexInfo::LevelMutexContainerCIter cit = mutexes.begin(); - cit != endSpot; - ++cit ) - { - const volatile LevelMutexInfo * mutex = *cit; - if ( nullptr == mutex ) - return MutexErrors::NullMutexPointer; - if ( currentLevel != mutex->GetLevel() ) - { - return ( LevelMutexInfo::UnlockedLevel == currentLevel ) ? - MutexErrors::NotRecentLock : MutexErrors::WrongLevel; - } - if ( !mutex->IsRecentLock( count ) ) - return MutexErrors::NotRecentLock; - if ( !IsUniqueMutex( mutexes, cit ) ) - return MutexErrors::DuplicateMutex; - } - - if ( count != CountMutexesAtCurrentLevel() ) - return MutexErrors::LevelTooHigh; - - return MutexErrors::Success; -} - -// ---------------------------------------------------------------------------- - -LevelMutexInfo::MutexUndoer::MutexUndoer( MutexContainer & mutexes ) : - m_mutexes( mutexes ), - m_here( mutexes.end() ) -{ - assert( this != nullptr ); -} - -// ---------------------------------------------------------------------------- - -LevelMutexInfo::MutexUndoer::~MutexUndoer( void ) -{ - assert( this != nullptr ); - try - { - if ( m_here == m_mutexes.end() ) - return; - LevelMutexContainerRIter rend( m_mutexes.rend() ); - LevelMutexContainerRIter rit( m_here ); - --rit; - for ( ; rit != rend; ++rit ) - { - volatile ::Loki::LevelMutexInfo * mutex = *rit; - assert( nullptr != mutex ); - mutex->UnlockThis(); - } - } - catch ( ... ) - { - } -} - -// ---------------------------------------------------------------------------- - -void LevelMutexInfo::MutexUndoer::SetPlace( LevelMutexContainerIter & here ) -{ - assert( this != nullptr ); - m_here = here; -} - -// ---------------------------------------------------------------------------- - -void LevelMutexInfo::MutexUndoer::Cancel( void ) -{ - assert( this != nullptr ); - m_here = m_mutexes.end(); -} - -// ---------------------------------------------------------------------------- - -const volatile LevelMutexInfo * LevelMutexInfo::GetCurrentMutex( void ) -{ - assert( IsValidList() ); - return s_currentMutex; -} - -// ---------------------------------------------------------------------------- - -bool LevelMutexInfo::IsValidList( void ) -{ - const volatile LevelMutexInfo * mutex1 = s_currentMutex; - const volatile LevelMutexInfo * mutex2 = s_currentMutex; - if ( nullptr == mutex1 ) - return true; - - while ( nullptr != mutex2 ) - { - if ( nullptr == mutex2 ) - break; - mutex2 = mutex2->m_previous; - if ( mutex1 == mutex2 ) - return false; - if ( nullptr == mutex2 ) - break; - mutex2 = mutex2->m_previous; - if ( mutex1 == mutex2 ) - return false; - if ( nullptr == mutex2 ) - break; - mutex1 = mutex1->m_previous; - if ( nullptr == mutex1 ) - break; - } - - mutex1 = s_currentMutex; - unsigned int level = mutex1->m_level; - while ( nullptr != mutex1 ) - { - if ( level > mutex1->m_level ) - return false; - level = mutex1->m_level; - mutex1 = mutex1->m_previous; - } - - return true; -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type LevelMutexInfo::MultiLock( MutexContainer & mutexes ) -{ - assert( IsValidList() ); - - const unsigned int count = mutexes.size(); - if ( count == 0 ) - return MutexErrors::EmptyContainer; - - LevelMutexContainerIter it( mutexes.begin() ); - volatile LevelMutexInfo * mutex = *it; - if ( nullptr == mutex ) - return MutexErrors::NullMutexPointer; - // Since the pointer to the first mutex is not NULL, save it so we use it - // to call the derived class and check for errors. - const volatile LevelMutexInfo * const first = mutex; - if ( !IsUniqueMutex( mutexes, it ) ) - return MutexErrors::DuplicateMutex; - const unsigned int checkLevel = mutex->GetLevel(); - const unsigned int currentLevel = GetCurrentThreadsLevel(); - if ( currentLevel < checkLevel ) - { - return first->DoErrorCheck( MutexErrors::LevelTooHigh ); - } - - const LevelMutexContainerIter end( mutexes.end() ); - if ( currentLevel == checkLevel ) - { - MutexErrors::Type result = DoMutexesMatchContainer( mutexes ); - if ( MutexErrors::Success != result ) - { - if ( LevelMutexInfo::UnlockedLevel == currentLevel ) - { - return first->DoErrorCheck( result ); - } - return first->DoErrorCheck( MutexErrors::LevelTooHigh ); - } - for ( it = mutexes.begin(); it != end; ++it ) - { - mutex = *it; - mutex->IncrementCount(); - } - return MutexErrors::Success; - } - assert( !mutex->IsRecentLock( count ) ); - - if ( 1 < count ) - { - for ( ++it; it != end; ++it ) - { - mutex = *it; - if ( nullptr == mutex ) - return first->DoErrorCheck( MutexErrors::NullMutexPointer ); - const unsigned int level = mutex->GetLevel(); - if ( checkLevel != level ) - return first->DoErrorCheck( MutexErrors::WrongLevel ); - if ( !IsUniqueMutex( mutexes, it ) ) - return first->DoErrorCheck( MutexErrors::DuplicateMutex ); - assert( !mutex->IsRecentLock( count ) ); - } - - it = mutexes.begin(); - ::std::sort( it, end ); - } - - MutexUndoer undoer( mutexes ); - for ( ; it != end; ++it ) - { - mutex = *it; - const MutexErrors::Type result = mutex->LockThis(); - if ( MutexErrors::Success != result ) - return first->DoErrorCheck( result ); - undoer.SetPlace( it ); - } - undoer.Cancel(); - - return MutexErrors::Success; -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type LevelMutexInfo::MultiLock( MutexContainer & mutexes, - unsigned int milliSeconds ) -{ - assert( IsValidList() ); - - if ( 0 == milliSeconds ) - return MultiLock( mutexes ); - const unsigned int count = mutexes.size(); - if ( 0 == count ) - return MutexErrors::EmptyContainer; - - LevelMutexContainerIter it( mutexes.begin() ); - volatile LevelMutexInfo * mutex = *it; - if ( nullptr == mutex ) - return MutexErrors::NullMutexPointer; - // Since the pointer to the first mutex is not NULL, save it so we use it - // to call the derived class and check for errors. - const volatile LevelMutexInfo * const first = mutex; - if ( !IsUniqueMutex( mutexes, it ) ) - return first->DoErrorCheck( MutexErrors::DuplicateMutex ); - const unsigned int checkLevel = mutex->GetLevel(); - const unsigned int currentLevel = GetCurrentThreadsLevel(); - if ( currentLevel < checkLevel ) - { - return first->DoErrorCheck( MutexErrors::LevelTooHigh ); - } - - const LevelMutexContainerIter end( mutexes.end() ); - if ( currentLevel == checkLevel ) - { - MutexErrors::Type result = DoMutexesMatchContainer( mutexes ); - if ( MutexErrors::Success != result ) - { - if ( LevelMutexInfo::UnlockedLevel == currentLevel ) - { - return first->DoErrorCheck( result ); - } - return first->DoErrorCheck( MutexErrors::LevelTooHigh ); - } - for ( it = mutexes.begin(); it != end; ++it ) - { - mutex = *it; - mutex->IncrementCount(); - } - return MutexErrors::Success; - } - assert( !mutex->IsRecentLock( count ) ); - - if ( 1 < count ) - { - for ( ++it; it != end; ++it ) - { - mutex = *it; - if ( nullptr == mutex ) - return first->DoErrorCheck( MutexErrors::NullMutexPointer ); - const unsigned int level = mutex->GetLevel(); - if ( checkLevel != level ) - return first->DoErrorCheck( MutexErrors::WrongLevel ); - if ( !IsUniqueMutex( mutexes, it ) ) - return first->DoErrorCheck( MutexErrors::DuplicateMutex ); - assert( !mutex->IsRecentLock( count ) ); - } - - it = mutexes.begin(); - ::std::sort( it, end ); - } - - MutexUndoer undoer( mutexes ); - for ( ; it != end; ++it ) - { - mutex = *it; - const MutexErrors::Type result = mutex->LockThis( milliSeconds ); - if ( MutexErrors::Success != result ) - return first->DoErrorCheck( result ); - undoer.SetPlace( it ); - } - undoer.Cancel(); - - return MutexErrors::Success; -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type LevelMutexInfo::MultiUnlock( MutexContainer & mutexes ) -{ - assert( IsValidList() ); - - MutexErrors::Type result = DoMutexesMatchContainer( mutexes ); - if ( result != MutexErrors::Success ) - { - const volatile LevelMutexInfo * const mutex = GetFirstMutex( mutexes ); - if ( nullptr != mutex ) - return mutex->DoErrorCheck( result ); - throw MutexException( "Unable to unlock mutexes in container.", - LevelMutexInfo::UnlockedLevel, result ); - } - - const unsigned int count = mutexes.size(); - if ( 1 < count ) - { - ::std::sort( mutexes.begin(), mutexes.end() ); - } - - bool failed = false; - LevelMutexContainerRIter rit( mutexes.rbegin() ); - const LevelMutexContainerRIter rend( mutexes.rend() ); - for ( ; rit != rend; ++rit ) - { - try - { - volatile LevelMutexInfo * mutex = *rit; - result = mutex->UnlockThis(); - if ( MutexErrors::Success != result ) - failed = true; - } - catch ( ... ) - { - failed = true; - // If one fails to unlock, keep trying to unlock the others. - // So don't just exit the for loop. This keeps going instead - // of trying to relock the mutex and exit since it is not - // safe to leave some locked, but not others. - } - } - - return ( failed ) ? MutexErrors::MultiUnlockFailed : MutexErrors::Success; -} - -// ---------------------------------------------------------------------------- - -LevelMutexInfo::LevelMutexInfo( unsigned int level ) : - m_level( level ), - m_count( 0 ), - m_previous( nullptr ) -{ - assert( IsValid() ); -} - -// ---------------------------------------------------------------------------- - -LevelMutexInfo::~LevelMutexInfo( void ) -{ - assert( IsValid() ); - assert( 0 == m_count ); - assert( nullptr == m_previous ); -} - -// ---------------------------------------------------------------------------- - -bool LevelMutexInfo::IsValid( void ) const volatile -{ - assert( nullptr != this ); - assert( LevelMutexInfo::UnlockedLevel != m_level ); - assert( m_previous != this ); - assert( ( nullptr == m_previous ) || ( 0 < m_count ) ); - assert( IsValidList() ); - return true; -} - -// ---------------------------------------------------------------------------- - -void LevelMutexInfo::IncrementCount( void ) volatile -{ - assert( IsValid() ); - assert( 0 < m_count ); - ++m_count; -} - -// ---------------------------------------------------------------------------- - -void LevelMutexInfo::DecrementCount( void ) volatile -{ - assert( IsValid() ); - assert( 0 < m_count ); - --m_count; -} - -// ---------------------------------------------------------------------------- - -bool LevelMutexInfo::IsLockedByCurrentThread( void ) const volatile -{ - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - if ( !IsLocked() ) - return false; - const volatile LevelMutexInfo * mutex = s_currentMutex; - while ( nullptr != mutex ) - { - if ( this == mutex ) - return true; - mutex = mutex->m_previous; - } - return false; -} - -// ---------------------------------------------------------------------------- - -bool LevelMutexInfo::IsRecentLock( void ) const volatile -{ - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - if ( 0 == m_count ) - return false; - const volatile LevelMutexInfo * mutex = s_currentMutex; - while ( nullptr != mutex ) - { - assert( m_level <= mutex->m_level ); - if ( this == mutex ) - return true; - if ( m_level != mutex->m_level ) - return false; - mutex = mutex->m_previous; - } - return false; -} - -// ---------------------------------------------------------------------------- - -bool LevelMutexInfo::IsRecentLock( unsigned int count ) const volatile -{ - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - if ( 0 == count ) - return false; - const volatile LevelMutexInfo * mutex = s_currentMutex; - for ( ; count > 0; count-- ) - { - if ( nullptr == mutex ) - return false; - if ( this == mutex ) - return true; - mutex = mutex->m_previous; - } - return false; -} - -// ---------------------------------------------------------------------------- - -bool LevelMutexInfo::IsLockedByAnotherThread( void ) const volatile -{ - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - if ( !IsLocked() ) - return false; - if ( IsLockedByCurrentThread() ) - return false; - if ( !IsLocked() ) - return false; - return true; -} - -// ---------------------------------------------------------------------------- - -void LevelMutexInfo::PostLock( void ) volatile -{ - assert( IsValid() ); - assert( 0 == m_count ); - assert( nullptr == m_previous ); - assert( this != s_currentMutex ); - assert( !IsLockedByCurrentThread() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - m_count = 1; - m_previous = s_currentMutex; - s_currentMutex = this; -} - -// ---------------------------------------------------------------------------- - -void LevelMutexInfo::PreUnlock( void ) volatile -{ - assert( IsValid() ); - assert( 1 == m_count ); - assert( nullptr != s_currentMutex ); - assert( this == s_currentMutex ); - assert( IsLockedByCurrentThread() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - s_currentMutex = m_previous; - m_previous = nullptr; - m_count = 0; -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type LevelMutexInfo::PreLockCheck( bool forTryLock ) volatile -{ - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - const unsigned int currentLevel = GetCurrentThreadsLevel(); - if ( currentLevel < LevelMutexInfo::GetLevel() ) - return MutexErrors::LevelTooHigh; - const bool lockedByThisThread = IsLockedByCurrentThread(); - if ( !lockedByThisThread && forTryLock && IsLocked() ) - return MutexErrors::AlreadyLocked; - if ( currentLevel == LevelMutexInfo::GetLevel() ) - { - // If this mutex has the same level as the current level, - // and was locked by the current thread, then assume it - // was locked with the MultiLock function. Which means it - // is safe to relock this. If this checked if it equals - // s_currentMutex that would defeat re-entrancy for all - // multi-locked mutexes. - if ( lockedByThisThread ) - { - m_count++; - return MutexErrors::Success; - } - else - { - return MutexErrors::LevelTooHigh; - } - } - - return MutexErrors::NoProblem; -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type LevelMutexInfo::PreUnlockCheck( void ) volatile -{ - assert( IsValid() ); - LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) - - if ( 0 == m_count ) - return MutexErrors::WasntLocked; - const unsigned int currentLevel = GetCurrentThreadsLevel(); - if ( currentLevel > m_level ) - return MutexErrors::LevelTooLow; - if ( currentLevel < m_level ) - return MutexErrors::LevelTooHigh; - const bool lockedByThisThread = IsLockedByCurrentThread(); - if ( !lockedByThisThread ) - return MutexErrors::NotLockedByThread; - if ( 1 < m_count ) - { - m_count--; - return MutexErrors::Success; - } - - return MutexErrors::NoProblem; -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type ThrowOnAnyMutexError::CheckError( MutexErrors::Type error, - unsigned int level ) -{ - if ( ( error != MutexErrors::Success ) - && ( error != MutexErrors::NoProblem ) ) - { - throw MutexException( "Error occurred using mutex.", level, error ); - } - return error; -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type ThrowOnBadDesignMutexError::CheckError( MutexErrors::Type error, - unsigned int level ) -{ - if ( ( error == MutexErrors::LevelTooHigh ) - && ( error == MutexErrors::LevelTooLow ) ) - { - throw MutexException( "Error occurred using mutex.", level, error ); - } - return error; -} - -// ---------------------------------------------------------------------------- - -void MutexSleepWaits::Wait( void ) -{ -#if defined( _MSC_VER ) - ::SleepEx( sleepTime, true ); -#else - ::sleep( sleepTime ); -#endif -} - -// ---------------------------------------------------------------------------- - -SpinLevelMutex::SpinLevelMutex( unsigned int level ) : - m_mutex(), - m_level( level ) -{ -#if defined( _MSC_VER ) - ::InitializeCriticalSection( &m_mutex ); -#else - const int result = ::pthread_mutex_init( &m_mutex, 0 ); - switch ( result ) - { - case 0: - return; - case EBUSY: - throw MutexException( "pthread mutex already initialized!", - level, MutexErrors::AlreadyInitialized ); - default: - case EINVAL: - throw MutexException( "pthread mutex has an invalid attribute!", - level, MutexErrors::InvalidAttribute ); - case EFAULT: - throw MutexException( "pthread mutex has an invalid address!", - level, MutexErrors::InvalidAddress ); - } -#endif -} - -// ---------------------------------------------------------------------------- - -SpinLevelMutex::~SpinLevelMutex( void ) -{ - try - { -#if defined( _MSC_VER ) - ::DeleteCriticalSection( &m_mutex ); -#else - ::pthread_mutex_destroy( &m_mutex ); -#endif - } - catch ( ... ) - { - // Not much we can do after catching an exception inside a destructor! - } -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type SpinLevelMutex::Lock( void ) volatile -{ - // Have to cast away volatile since Windows CriticalSection class does not - // use volatile qualifier. - SpinLevelMutex * pThis = const_cast< SpinLevelMutex * >( this ); -#if defined( _MSC_VER ) - ::EnterCriticalSection( &pThis->m_mutex ); -#else - const int result = ::pthread_mutex_lock( &pThis->m_mutex ); - switch ( result ) - { - case 0: - break; - default: - case EINVAL: - throw MutexException( "pthread mutex not initialized properly!", - GetLevel(), MutexErrors::NotInitialized ); - case EFAULT : - throw MutexException( "pthread mutex is not valid!", - GetLevel(), MutexErrors::InvalidAddress ); - case EDEADLK: - throw MutexException( "locking this pthread mutex may cause a deadlock!", - GetLevel(), MutexErrors::MayDeadlock ); - } -#endif - return MutexErrors::Success; -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type SpinLevelMutex::TryLock( void ) volatile -{ - // Have to cast away volatile since Windows CriticalSection class does not - // use volatile qualifier. - SpinLevelMutex * pThis = const_cast< SpinLevelMutex * >( this ); -#if defined( _MSC_VER ) - const bool locked = ( 0 != ::TryEnterCriticalSection( &pThis->m_mutex ) ); - return ( locked ) ? MutexErrors::Success : MutexErrors::TryFailed; -#else - const int result = ::pthread_mutex_trylock( &pThis->m_mutex ); - switch ( result ) - { - case 0: - return MutexErrors::Success; - default: - case EBUSY: - break; - case EAGAIN: - throw MutexException( "pthread mutex reached recursion limit!", - GetLevel(), MutexErrors::TooMuchRecursion ); - } - return MutexErrors::TryFailed; -#endif -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type SpinLevelMutex::Unlock( void ) volatile -{ - // Have to cast away volatile since Windows CriticalSection class does not - // use volatile qualifier. - SpinLevelMutex * pThis = const_cast< SpinLevelMutex * >( this ); -#if defined( _MSC_VER ) - ::LeaveCriticalSection( &pThis->m_mutex ); -#else - const int result = ::pthread_mutex_unlock( &pThis->m_mutex ); - if ( EPERM == result ) - throw MutexException( "current thread did not lock this pthread mutex!", - GetLevel(), MutexErrors::NotLockedByThread ); -#endif - return MutexErrors::Success; -} - -// ---------------------------------------------------------------------------- - -#if defined( _MSC_VER ) - -SleepLevelMutex::SleepLevelMutex( unsigned int level ) : - SpinLevelMutex( level ), - m_sleepTime( 1 ), - m_wakable( true ) -{ -} - -// ---------------------------------------------------------------------------- - -#else - -SleepLevelMutex::SleepLevelMutex( unsigned int level, unsigned int sleepTime ) : - SpinLevelMutex( level ), - m_sleepTime( sleepTime / 1000 ) -{ - if ( 0 == m_sleepTime ) - m_sleepTime = 1; // Can't have a resolution less than 1 second. -} - -#endif - -// ---------------------------------------------------------------------------- - -SleepLevelMutex::~SleepLevelMutex( void ) -{ -} - -// ---------------------------------------------------------------------------- - -MutexErrors::Type SleepLevelMutex::Lock( void ) volatile -{ - bool locked = false; - while ( !locked ) - { - locked = ( MutexErrors::Success == TryLock() ); - if ( locked ) - break; -#if defined( _MSC_VER ) - ::SleepEx( m_sleepTime, m_wakable ); -#else - ::sleep( m_sleepTime ); -#endif - } - return MutexErrors::Success; -} - -// ---------------------------------------------------------------------------- - -MutexException::MutexException( const char * message, - unsigned int level, MutexErrors::Type reason ) : - m_message( message ), - m_level( level ), - m_reason( reason ) -{ -} - -// ---------------------------------------------------------------------------- - -MutexException::MutexException( const MutexException & that ) throw () : - m_message( that.m_message ), - m_level( that.m_level ), - m_reason( that.m_reason ) -{ -} - -// ---------------------------------------------------------------------------- - -MutexException & MutexException::operator = ( const MutexException & that ) throw () -{ - m_message = that.m_message; - m_level = that.m_level; - m_reason = that.m_reason; - return *this; -} - -// ---------------------------------------------------------------------------- - -MutexException::~MutexException( void ) throw () -{ -} - -// ---------------------------------------------------------------------------- - -const char * MutexException::what( void ) const throw () -{ - return m_message; -} - -// ---------------------------------------------------------------------------- - -MutexLocker::MutexLocker( volatile LevelMutexInfo & mutex, bool lock ) : - m_mutex( mutex ), - m_locked( false ) -{ - assert( nullptr != this ); - if ( !lock ) - return; - const MutexErrors::Type result = mutex.Lock(); - m_locked = ( MutexErrors::Success == result ); - if ( !m_locked ) - throw MutexException( "Unable to lock mutex.", mutex.GetLevel(), result ); -} - -// ---------------------------------------------------------------------------- - -MutexLocker::MutexLocker( volatile LevelMutexInfo & mutex, unsigned int milliSeconds, - bool lock ) : - m_mutex( mutex ), - m_locked( false ) -{ - assert( nullptr != this ); - if ( !lock ) - return; - const MutexErrors::Type result = mutex.Lock( milliSeconds ); - m_locked = ( MutexErrors::Success == result ); - if ( !m_locked ) - throw MutexException( "Unable to lock mutex.", mutex.GetLevel(), result ); -} - -// ---------------------------------------------------------------------------- - -MutexLocker::~MutexLocker( void ) -{ - assert( nullptr != this ); - if ( !m_locked ) - return; - try - { - m_mutex.Unlock(); - } - catch ( ... ) - { - // Not much we can do when catching an exception inside a destructor. - } -} - -// ---------------------------------------------------------------------------- - -bool MutexLocker::Lock( void ) -{ - assert( nullptr != this ); - if ( m_locked ) - return true; - const MutexErrors::Type result = m_mutex.Lock(); - if ( MutexErrors::Success != result ) - return false; - m_locked = true; - return true; -} - -// ---------------------------------------------------------------------------- - -bool MutexLocker::Unlock( void ) -{ - assert( nullptr != this ); - if ( !m_locked ) - return true; - const MutexErrors::Type result = m_mutex.Unlock(); - if ( MutexErrors::Success != result ) - return false; - m_locked = false; - return true; -} - -// ---------------------------------------------------------------------------- - -MultiMutexLocker::MultiMutexLocker( LevelMutexInfo::MutexContainer & mutexes, - bool lock ) : - m_mutexes( mutexes ), - m_locked( false ) -{ - assert( nullptr != this ); - if ( !lock ) - return; - const MutexErrors::Type result = LevelMutexInfo::MultiLock( mutexes ); - m_locked = ( MutexErrors::Success == result ); - if ( !m_locked ) - throw MutexException( "Unable to lock multiple mutexes.", - GetLevel( mutexes ), result ); -} - -// ---------------------------------------------------------------------------- - -MultiMutexLocker::MultiMutexLocker( LevelMutexInfo::MutexContainer & mutexes, - unsigned int milliSeconds, bool lock ) : - m_mutexes( mutexes ), - m_locked( false ) -{ - assert( nullptr != this ); - if ( !lock ) - return; - const MutexErrors::Type result = LevelMutexInfo::MultiLock( mutexes, milliSeconds ); - m_locked = ( MutexErrors::Success == result ); - if ( !m_locked ) - throw MutexException( "Unable to lock multiple mutexes.", - GetLevel( mutexes ), result ); -} - -// ---------------------------------------------------------------------------- - -MultiMutexLocker::~MultiMutexLocker( void ) -{ - assert( nullptr != this ); - if ( !m_locked ) - return; - try - { - LevelMutexInfo::MultiUnlock( m_mutexes ); - } - catch ( ... ) - { - // Not much we can do when catching an exception inside a destructor. - } -} - -// ---------------------------------------------------------------------------- - -bool MultiMutexLocker::Lock( void ) -{ - assert( nullptr != this ); - if ( m_locked ) - return true; - const MutexErrors::Type result = LevelMutexInfo::MultiLock( m_mutexes ); - if ( MutexErrors::Success != result ) - return false; - m_locked = true; - return true; -} - -// ---------------------------------------------------------------------------- - -bool MultiMutexLocker::Unlock( void ) -{ - assert( nullptr != this ); - if ( !m_locked ) - return true; - const MutexErrors::Type result = LevelMutexInfo::MultiUnlock( m_mutexes ); - if ( MutexErrors::Success != result ) - return false; - m_locked = false; - return true; -} - -// ---------------------------------------------------------------------------- - -}; // end namespace Loki +//////////////////////////////////////////////////////////////////////////////// +// +// LevelMutex facility for the Loki Library +// Copyright (c) 2008 Richard Sposato +// The copyright on this file is protected under the terms of the MIT license. +// +// Permission to use, copy, modify, distribute and sell this software for any +// purpose is hereby granted without fee, provided that the above copyright +// notice appear in all copies and that both that copyright notice and this +// permission notice appear in supporting documentation. +// +// The author makes no representations about the suitability of this software +// for any purpose. It is provided "as is" without express or implied warranty. +// +//////////////////////////////////////////////////////////////////////////////// + +// $Id$ + +/// @file LevelMutex.cpp Contains functions needed by LevelMutex class. + +// ---------------------------------------------------------------------------- + +#include + +#include +#include + + +using namespace ::std; + +// define nullptr even though new compilers will have this keyword just so we +// have a consistent and easy way of identifying which uses of 0 mean null. +#define nullptr 0 + + +LOKI_THREAD_LOCAL volatile ::Loki::LevelMutexInfo * ::Loki::LevelMutexInfo::s_currentMutex = nullptr; + +unsigned int ::Loki::MutexSleepWaits::sleepTime = 1; + + +/// Anonymous namespace hides some functions which are implementation details. +namespace +{ + +// ---------------------------------------------------------------------------- + +/** Determines if the mutex at specific iterator location is unique within the +container of mutexes. It only checks mutexes at later locations in the +container instead of the entire container partly for efficiency sake. (Any +prior duplications would have gotten caught during earlier calls to this +function.) This should not throw exceptions. It requires O(m) operations +where m is the number of elements in the container after the iterator. + @param mutexes Container to check. + @param cit Location of mutex used for comparing. + @return True for uniqueness, false if a duplicate exists. + */ +bool IsUniqueMutex( const ::Loki::LevelMutexInfo::MutexContainer & mutexes, + ::Loki::LevelMutexInfo::LevelMutexContainerCIter cit ) +{ + assert( mutexes.end() != cit ); + + const ::Loki::LevelMutexInfo::LevelMutexContainerCIter end = mutexes.end(); + const volatile ::Loki::LevelMutexInfo * mutex = *cit; + for ( ++cit; cit != end; ++cit ) + { + const volatile ::Loki::LevelMutexInfo * check = *cit; + if ( check == mutex ) + return false; + } + return true; +} + +// ---------------------------------------------------------------------------- + +/** Returns pointer to first mutex it finds in the container. This should not + throw, and takes O(1) most of the time. At worse, it takes O(m) operations + where m is the size of the container. + @param mutexes Container of mutexes. + @return Pointer to first mutex it finds, or nullptr if container is empty or + each element is a nullptr. + */ +const volatile ::Loki::LevelMutexInfo * GetFirstMutex( + const ::Loki::LevelMutexInfo::MutexContainer & mutexes ) +{ + if ( mutexes.size() == 0 ) + return nullptr; + ::Loki::LevelMutexInfo::LevelMutexContainerCIter it( mutexes.begin() ); + const volatile ::Loki::LevelMutexInfo * mutex = *it; + if ( nullptr != mutex ) + return mutex; + + const ::Loki::LevelMutexInfo::LevelMutexContainerCIter end( mutexes.end() ); + while ( it != end ) + { + mutex = *it; + if ( nullptr != mutex ) + return mutex; + ++it; + } + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +/** Gets the level number associated with the first mutex found in a container. + Usually takes O(1) operations, but take up to O(m) where m is the size of the + container. + @return Level number of first mutex in container, or UnlockedLevel if no + mutexes were found in the container. + */ +unsigned int GetLevel( const ::Loki::LevelMutexInfo::MutexContainer & mutexes ) +{ + const volatile ::Loki::LevelMutexInfo * mutex = GetFirstMutex( mutexes ); + return ( nullptr == mutex ) ? ::Loki::LevelMutexInfo::UnlockedLevel : mutex->GetLevel(); +} + +// ---------------------------------------------------------------------------- + +} // end anonymous namespace + +namespace Loki +{ + +// ---------------------------------------------------------------------------- + +unsigned int GetCurrentThreadsLevel( void ) +{ + const volatile LevelMutexInfo * mutex = LevelMutexInfo::GetCurrentMutex(); + return ( nullptr == mutex ) ? LevelMutexInfo::UnlockedLevel : mutex->GetLevel(); +} + +// ---------------------------------------------------------------------------- + +unsigned int CountMutexesInCurrentThread( void ) +{ + const volatile LevelMutexInfo * mutex = LevelMutexInfo::GetCurrentMutex(); + unsigned int count = 0; + while ( nullptr != mutex ) + { + count++; + mutex = mutex->GetPrevious(); + } + return count; +} + +// ---------------------------------------------------------------------------- + +unsigned int CountLocksInCurrentThread( void ) +{ + const volatile LevelMutexInfo * mutex = LevelMutexInfo::GetCurrentMutex(); + unsigned int count = 0; + while ( nullptr != mutex ) + { + count += mutex->GetLockCount(); + mutex = mutex->GetPrevious(); + } + return count; +} + +// ---------------------------------------------------------------------------- + +unsigned int CountMutexesAtCurrentLevel( void ) +{ + const volatile LevelMutexInfo * mutex = LevelMutexInfo::GetCurrentMutex(); + if ( nullptr == mutex ) + return 0; + unsigned int count = 0; + unsigned int level = mutex->GetLevel(); + while ( nullptr != mutex ) + { + if ( level != mutex->GetLevel() ) + break; + mutex = mutex->GetPrevious(); + count++; + } + return count; +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type DoMutexesMatchContainer( const LevelMutexInfo::MutexContainer & mutexes ) +{ + const unsigned int count = mutexes.size(); + if ( 0 == count ) + return MutexErrors::EmptyContainer; + unsigned int currentLevel = GetCurrentThreadsLevel(); + const LevelMutexInfo::LevelMutexContainerCIter endSpot = mutexes.end(); + + for ( LevelMutexInfo::LevelMutexContainerCIter cit = mutexes.begin(); + cit != endSpot; + ++cit ) + { + const volatile LevelMutexInfo * mutex = *cit; + if ( nullptr == mutex ) + return MutexErrors::NullMutexPointer; + if ( currentLevel != mutex->GetLevel() ) + { + return ( LevelMutexInfo::UnlockedLevel == currentLevel ) ? + MutexErrors::NotRecentLock : MutexErrors::WrongLevel; + } + if ( !mutex->IsRecentLock( count ) ) + return MutexErrors::NotRecentLock; + if ( !IsUniqueMutex( mutexes, cit ) ) + return MutexErrors::DuplicateMutex; + } + + if ( count != CountMutexesAtCurrentLevel() ) + return MutexErrors::LevelTooHigh; + + return MutexErrors::Success; +} + +// ---------------------------------------------------------------------------- + +LevelMutexInfo::MutexUndoer::MutexUndoer( MutexContainer & mutexes ) : + m_mutexes( mutexes ), + m_here( mutexes.end() ) +{ + assert( this != nullptr ); +} + +// ---------------------------------------------------------------------------- + +LevelMutexInfo::MutexUndoer::~MutexUndoer( void ) +{ + assert( this != nullptr ); + try + { + if ( m_here == m_mutexes.end() ) + return; + LevelMutexContainerRIter rend( m_mutexes.rend() ); + LevelMutexContainerRIter rit( m_here ); + --rit; + for ( ; rit != rend; ++rit ) + { + volatile ::Loki::LevelMutexInfo * mutex = *rit; + assert( nullptr != mutex ); + mutex->UnlockThis(); + } + } + catch ( ... ) + { + } +} + +// ---------------------------------------------------------------------------- + +void LevelMutexInfo::MutexUndoer::SetPlace( LevelMutexContainerIter & here ) +{ + assert( this != nullptr ); + m_here = here; +} + +// ---------------------------------------------------------------------------- + +void LevelMutexInfo::MutexUndoer::Cancel( void ) +{ + assert( this != nullptr ); + m_here = m_mutexes.end(); +} + +// ---------------------------------------------------------------------------- + +const volatile LevelMutexInfo * LevelMutexInfo::GetCurrentMutex( void ) +{ + assert( IsValidList() ); + return s_currentMutex; +} + +// ---------------------------------------------------------------------------- + +bool LevelMutexInfo::IsValidList( void ) +{ + const volatile LevelMutexInfo * mutex1 = s_currentMutex; + const volatile LevelMutexInfo * mutex2 = s_currentMutex; + if ( nullptr == mutex1 ) + return true; + + while ( nullptr != mutex2 ) + { + if ( nullptr == mutex2 ) + break; + mutex2 = mutex2->m_previous; + if ( mutex1 == mutex2 ) + return false; + if ( nullptr == mutex2 ) + break; + mutex2 = mutex2->m_previous; + if ( mutex1 == mutex2 ) + return false; + if ( nullptr == mutex2 ) + break; + mutex1 = mutex1->m_previous; + if ( nullptr == mutex1 ) + break; + } + + mutex1 = s_currentMutex; + unsigned int level = mutex1->m_level; + while ( nullptr != mutex1 ) + { + if ( level > mutex1->m_level ) + return false; + level = mutex1->m_level; + mutex1 = mutex1->m_previous; + } + + return true; +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type LevelMutexInfo::MultiLock( MutexContainer & mutexes ) +{ + assert( IsValidList() ); + + const unsigned int count = mutexes.size(); + if ( count == 0 ) + return MutexErrors::EmptyContainer; + + LevelMutexContainerIter it( mutexes.begin() ); + volatile LevelMutexInfo * mutex = *it; + if ( nullptr == mutex ) + return MutexErrors::NullMutexPointer; + // Since the pointer to the first mutex is not NULL, save it so we use it + // to call the derived class and check for errors. + const volatile LevelMutexInfo * const first = mutex; + if ( !IsUniqueMutex( mutexes, it ) ) + return MutexErrors::DuplicateMutex; + const unsigned int checkLevel = mutex->GetLevel(); + const unsigned int currentLevel = GetCurrentThreadsLevel(); + if ( currentLevel < checkLevel ) + { + return first->DoErrorCheck( MutexErrors::LevelTooHigh ); + } + + const LevelMutexContainerIter end( mutexes.end() ); + if ( currentLevel == checkLevel ) + { + MutexErrors::Type result = DoMutexesMatchContainer( mutexes ); + if ( MutexErrors::Success != result ) + { + if ( LevelMutexInfo::UnlockedLevel == currentLevel ) + { + return first->DoErrorCheck( result ); + } + return first->DoErrorCheck( MutexErrors::LevelTooHigh ); + } + for ( it = mutexes.begin(); it != end; ++it ) + { + mutex = *it; + mutex->IncrementCount(); + } + return MutexErrors::Success; + } + assert( !mutex->IsRecentLock( count ) ); + + if ( 1 < count ) + { + for ( ++it; it != end; ++it ) + { + mutex = *it; + if ( nullptr == mutex ) + return first->DoErrorCheck( MutexErrors::NullMutexPointer ); + const unsigned int level = mutex->GetLevel(); + if ( checkLevel != level ) + return first->DoErrorCheck( MutexErrors::WrongLevel ); + if ( !IsUniqueMutex( mutexes, it ) ) + return first->DoErrorCheck( MutexErrors::DuplicateMutex ); + assert( !mutex->IsRecentLock( count ) ); + } + + it = mutexes.begin(); + ::std::sort( it, end ); + } + + MutexUndoer undoer( mutexes ); + for ( ; it != end; ++it ) + { + mutex = *it; + const MutexErrors::Type result = mutex->LockThis(); + if ( MutexErrors::Success != result ) + return first->DoErrorCheck( result ); + undoer.SetPlace( it ); + } + undoer.Cancel(); + + return MutexErrors::Success; +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type LevelMutexInfo::MultiLock( MutexContainer & mutexes, + unsigned int milliSeconds ) +{ + assert( IsValidList() ); + + if ( 0 == milliSeconds ) + return MultiLock( mutexes ); + const unsigned int count = mutexes.size(); + if ( 0 == count ) + return MutexErrors::EmptyContainer; + + LevelMutexContainerIter it( mutexes.begin() ); + volatile LevelMutexInfo * mutex = *it; + if ( nullptr == mutex ) + return MutexErrors::NullMutexPointer; + // Since the pointer to the first mutex is not NULL, save it so we use it + // to call the derived class and check for errors. + const volatile LevelMutexInfo * const first = mutex; + if ( !IsUniqueMutex( mutexes, it ) ) + return first->DoErrorCheck( MutexErrors::DuplicateMutex ); + const unsigned int checkLevel = mutex->GetLevel(); + const unsigned int currentLevel = GetCurrentThreadsLevel(); + if ( currentLevel < checkLevel ) + { + return first->DoErrorCheck( MutexErrors::LevelTooHigh ); + } + + const LevelMutexContainerIter end( mutexes.end() ); + if ( currentLevel == checkLevel ) + { + MutexErrors::Type result = DoMutexesMatchContainer( mutexes ); + if ( MutexErrors::Success != result ) + { + if ( LevelMutexInfo::UnlockedLevel == currentLevel ) + { + return first->DoErrorCheck( result ); + } + return first->DoErrorCheck( MutexErrors::LevelTooHigh ); + } + for ( it = mutexes.begin(); it != end; ++it ) + { + mutex = *it; + mutex->IncrementCount(); + } + return MutexErrors::Success; + } + assert( !mutex->IsRecentLock( count ) ); + + if ( 1 < count ) + { + for ( ++it; it != end; ++it ) + { + mutex = *it; + if ( nullptr == mutex ) + return first->DoErrorCheck( MutexErrors::NullMutexPointer ); + const unsigned int level = mutex->GetLevel(); + if ( checkLevel != level ) + return first->DoErrorCheck( MutexErrors::WrongLevel ); + if ( !IsUniqueMutex( mutexes, it ) ) + return first->DoErrorCheck( MutexErrors::DuplicateMutex ); + assert( !mutex->IsRecentLock( count ) ); + } + + it = mutexes.begin(); + ::std::sort( it, end ); + } + + MutexUndoer undoer( mutexes ); + for ( ; it != end; ++it ) + { + mutex = *it; + const MutexErrors::Type result = mutex->LockThis( milliSeconds ); + if ( MutexErrors::Success != result ) + return first->DoErrorCheck( result ); + undoer.SetPlace( it ); + } + undoer.Cancel(); + + return MutexErrors::Success; +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type LevelMutexInfo::MultiUnlock( MutexContainer & mutexes ) +{ + assert( IsValidList() ); + + MutexErrors::Type result = DoMutexesMatchContainer( mutexes ); + if ( result != MutexErrors::Success ) + { + const volatile LevelMutexInfo * const mutex = GetFirstMutex( mutexes ); + if ( nullptr != mutex ) + return mutex->DoErrorCheck( result ); + throw MutexException( "Unable to unlock mutexes in container.", + LevelMutexInfo::UnlockedLevel, result ); + } + + const unsigned int count = mutexes.size(); + if ( 1 < count ) + { + ::std::sort( mutexes.begin(), mutexes.end() ); + } + + bool failed = false; + LevelMutexContainerRIter rit( mutexes.rbegin() ); + const LevelMutexContainerRIter rend( mutexes.rend() ); + for ( ; rit != rend; ++rit ) + { + try + { + volatile LevelMutexInfo * mutex = *rit; + result = mutex->UnlockThis(); + if ( MutexErrors::Success != result ) + failed = true; + } + catch ( ... ) + { + failed = true; + // If one fails to unlock, keep trying to unlock the others. + // So don't just exit the for loop. This keeps going instead + // of trying to relock the mutex and exit since it is not + // safe to leave some locked, but not others. + } + } + + return ( failed ) ? MutexErrors::MultiUnlockFailed : MutexErrors::Success; +} + +// ---------------------------------------------------------------------------- + +LevelMutexInfo::LevelMutexInfo( unsigned int level ) : + m_level( level ), + m_count( 0 ), + m_previous( nullptr ) +{ + assert( IsValid() ); +} + +// ---------------------------------------------------------------------------- + +LevelMutexInfo::~LevelMutexInfo( void ) +{ + assert( IsValid() ); + assert( 0 == m_count ); + assert( nullptr == m_previous ); +} + +// ---------------------------------------------------------------------------- + +bool LevelMutexInfo::IsValid( void ) const volatile +{ + assert( nullptr != this ); + assert( LevelMutexInfo::UnlockedLevel != m_level ); + assert( m_previous != this ); + assert( ( nullptr == m_previous ) || ( 0 < m_count ) ); + assert( IsValidList() ); + return true; +} + +// ---------------------------------------------------------------------------- + +void LevelMutexInfo::IncrementCount( void ) volatile +{ + assert( IsValid() ); + assert( 0 < m_count ); + ++m_count; +} + +// ---------------------------------------------------------------------------- + +void LevelMutexInfo::DecrementCount( void ) volatile +{ + assert( IsValid() ); + assert( 0 < m_count ); + --m_count; +} + +// ---------------------------------------------------------------------------- + +bool LevelMutexInfo::IsLockedByCurrentThread( void ) const volatile +{ + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + if ( !IsLocked() ) + return false; + const volatile LevelMutexInfo * mutex = s_currentMutex; + while ( nullptr != mutex ) + { + if ( this == mutex ) + return true; + mutex = mutex->m_previous; + } + return false; +} + +// ---------------------------------------------------------------------------- + +bool LevelMutexInfo::IsRecentLock( void ) const volatile +{ + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + if ( 0 == m_count ) + return false; + const volatile LevelMutexInfo * mutex = s_currentMutex; + while ( nullptr != mutex ) + { + assert( m_level <= mutex->m_level ); + if ( this == mutex ) + return true; + if ( m_level != mutex->m_level ) + return false; + mutex = mutex->m_previous; + } + return false; +} + +// ---------------------------------------------------------------------------- + +bool LevelMutexInfo::IsRecentLock( unsigned int count ) const volatile +{ + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + if ( 0 == count ) + return false; + const volatile LevelMutexInfo * mutex = s_currentMutex; + for ( ; count > 0; count-- ) + { + if ( nullptr == mutex ) + return false; + if ( this == mutex ) + return true; + mutex = mutex->m_previous; + } + return false; +} + +// ---------------------------------------------------------------------------- + +bool LevelMutexInfo::IsLockedByAnotherThread( void ) const volatile +{ + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + if ( !IsLocked() ) + return false; + if ( IsLockedByCurrentThread() ) + return false; + if ( !IsLocked() ) + return false; + return true; +} + +// ---------------------------------------------------------------------------- + +void LevelMutexInfo::PostLock( void ) volatile +{ + assert( IsValid() ); + assert( 0 == m_count ); + assert( nullptr == m_previous ); + assert( this != s_currentMutex ); + assert( !IsLockedByCurrentThread() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + m_count = 1; + m_previous = s_currentMutex; + s_currentMutex = this; +} + +// ---------------------------------------------------------------------------- + +void LevelMutexInfo::PreUnlock( void ) volatile +{ + assert( IsValid() ); + assert( 1 == m_count ); + assert( nullptr != s_currentMutex ); + assert( this == s_currentMutex ); + assert( IsLockedByCurrentThread() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + s_currentMutex = m_previous; + m_previous = nullptr; + m_count = 0; +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type LevelMutexInfo::PreLockCheck( bool forTryLock ) volatile +{ + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + const unsigned int currentLevel = GetCurrentThreadsLevel(); + if ( currentLevel < LevelMutexInfo::GetLevel() ) + return MutexErrors::LevelTooHigh; + const bool lockedByThisThread = IsLockedByCurrentThread(); + if ( !lockedByThisThread && forTryLock && IsLocked() ) + return MutexErrors::AlreadyLocked; + if ( currentLevel == LevelMutexInfo::GetLevel() ) + { + // If this mutex has the same level as the current level, + // and was locked by the current thread, then assume it + // was locked with the MultiLock function. Which means it + // is safe to relock this. If this checked if it equals + // s_currentMutex that would defeat re-entrancy for all + // multi-locked mutexes. + if ( lockedByThisThread ) + { + m_count++; + return MutexErrors::Success; + } + else + { + return MutexErrors::LevelTooHigh; + } + } + + return MutexErrors::NoProblem; +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type LevelMutexInfo::PreUnlockCheck( void ) volatile +{ + assert( IsValid() ); + LOKI_MUTEX_DEBUG_CODE( Checker checker( this ); (void)checker; ) + + if ( 0 == m_count ) + return MutexErrors::WasntLocked; + const unsigned int currentLevel = GetCurrentThreadsLevel(); + if ( currentLevel > m_level ) + return MutexErrors::LevelTooLow; + if ( currentLevel < m_level ) + return MutexErrors::LevelTooHigh; + const bool lockedByThisThread = IsLockedByCurrentThread(); + if ( !lockedByThisThread ) + return MutexErrors::NotLockedByThread; + if ( 1 < m_count ) + { + m_count--; + return MutexErrors::Success; + } + + return MutexErrors::NoProblem; +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type ThrowOnAnyMutexError::CheckError( MutexErrors::Type error, + unsigned int level ) +{ + if ( ( error != MutexErrors::Success ) + && ( error != MutexErrors::NoProblem ) ) + { + throw MutexException( "Error occurred using mutex.", level, error ); + } + return error; +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type ThrowOnBadDesignMutexError::CheckError( MutexErrors::Type error, + unsigned int level ) +{ + if ( ( error == MutexErrors::LevelTooHigh ) + && ( error == MutexErrors::LevelTooLow ) ) + { + throw MutexException( "Error occurred using mutex.", level, error ); + } + return error; +} + +// ---------------------------------------------------------------------------- + +void MutexSleepWaits::Wait( void ) +{ +#if defined( _MSC_VER ) + ::SleepEx( sleepTime, true ); +#else + ::sleep( sleepTime ); +#endif +} + +// ---------------------------------------------------------------------------- + +SpinLevelMutex::SpinLevelMutex( unsigned int level ) : + m_mutex(), + m_level( level ) +{ +#if defined( _MSC_VER ) + ::InitializeCriticalSection( &m_mutex ); +#else + const int result = ::pthread_mutex_init( &m_mutex, 0 ); + switch ( result ) + { + case 0: + return; + case EBUSY: + throw MutexException( "pthread mutex already initialized!", + level, MutexErrors::AlreadyInitialized ); + default: + case EINVAL: + throw MutexException( "pthread mutex has an invalid attribute!", + level, MutexErrors::InvalidAttribute ); + case EFAULT: + throw MutexException( "pthread mutex has an invalid address!", + level, MutexErrors::InvalidAddress ); + } +#endif +} + +// ---------------------------------------------------------------------------- + +SpinLevelMutex::~SpinLevelMutex( void ) +{ + try + { +#if defined( _MSC_VER ) + ::DeleteCriticalSection( &m_mutex ); +#else + ::pthread_mutex_destroy( &m_mutex ); +#endif + } + catch ( ... ) + { + // Not much we can do after catching an exception inside a destructor! + } +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type SpinLevelMutex::Lock( void ) volatile +{ + // Have to cast away volatile since Windows CriticalSection class does not + // use volatile qualifier. + SpinLevelMutex * pThis = const_cast< SpinLevelMutex * >( this ); +#if defined( _MSC_VER ) + ::EnterCriticalSection( &pThis->m_mutex ); +#else + const int result = ::pthread_mutex_lock( &pThis->m_mutex ); + switch ( result ) + { + case 0: + break; + default: + case EINVAL: + throw MutexException( "pthread mutex not initialized properly!", + GetLevel(), MutexErrors::NotInitialized ); + case EFAULT : + throw MutexException( "pthread mutex is not valid!", + GetLevel(), MutexErrors::InvalidAddress ); + case EDEADLK: + throw MutexException( "locking this pthread mutex may cause a deadlock!", + GetLevel(), MutexErrors::MayDeadlock ); + } +#endif + return MutexErrors::Success; +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type SpinLevelMutex::TryLock( void ) volatile +{ + // Have to cast away volatile since Windows CriticalSection class does not + // use volatile qualifier. + SpinLevelMutex * pThis = const_cast< SpinLevelMutex * >( this ); +#if defined( _MSC_VER ) + const bool locked = ( 0 != ::TryEnterCriticalSection( &pThis->m_mutex ) ); + return ( locked ) ? MutexErrors::Success : MutexErrors::TryFailed; +#else + const int result = ::pthread_mutex_trylock( &pThis->m_mutex ); + switch ( result ) + { + case 0: + return MutexErrors::Success; + default: + case EBUSY: + break; + case EAGAIN: + throw MutexException( "pthread mutex reached recursion limit!", + GetLevel(), MutexErrors::TooMuchRecursion ); + } + return MutexErrors::TryFailed; +#endif +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type SpinLevelMutex::Unlock( void ) volatile +{ + // Have to cast away volatile since Windows CriticalSection class does not + // use volatile qualifier. + SpinLevelMutex * pThis = const_cast< SpinLevelMutex * >( this ); +#if defined( _MSC_VER ) + ::LeaveCriticalSection( &pThis->m_mutex ); +#else + const int result = ::pthread_mutex_unlock( &pThis->m_mutex ); + if ( EPERM == result ) + throw MutexException( "current thread did not lock this pthread mutex!", + GetLevel(), MutexErrors::NotLockedByThread ); +#endif + return MutexErrors::Success; +} + +// ---------------------------------------------------------------------------- + +#if defined( _MSC_VER ) + +SleepLevelMutex::SleepLevelMutex( unsigned int level ) : + SpinLevelMutex( level ), + m_sleepTime( 1 ), + m_wakable( true ) +{ +} + +// ---------------------------------------------------------------------------- + +#else + +SleepLevelMutex::SleepLevelMutex( unsigned int level, unsigned int sleepTime ) : + SpinLevelMutex( level ), + m_sleepTime( sleepTime / 1000 ) +{ + if ( 0 == m_sleepTime ) + m_sleepTime = 1; // Can't have a resolution less than 1 second. +} + +#endif + +// ---------------------------------------------------------------------------- + +SleepLevelMutex::~SleepLevelMutex( void ) +{ +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type SleepLevelMutex::Lock( void ) volatile +{ + bool locked = false; + while ( !locked ) + { + locked = ( MutexErrors::Success == TryLock() ); + if ( locked ) + break; +#if defined( _MSC_VER ) + ::SleepEx( m_sleepTime, m_wakable ); +#else + ::sleep( m_sleepTime ); +#endif + } + return MutexErrors::Success; +} + +// ---------------------------------------------------------------------------- + +MutexException::MutexException( const char * message, + unsigned int level, MutexErrors::Type reason ) : + m_message( message ), + m_level( level ), + m_reason( reason ) +{ +} + +// ---------------------------------------------------------------------------- + +MutexException::MutexException( const MutexException & that ) throw () : + m_message( that.m_message ), + m_level( that.m_level ), + m_reason( that.m_reason ) +{ +} + +// ---------------------------------------------------------------------------- + +MutexException & MutexException::operator = ( const MutexException & that ) throw () +{ + m_message = that.m_message; + m_level = that.m_level; + m_reason = that.m_reason; + return *this; +} + +// ---------------------------------------------------------------------------- + +MutexException::~MutexException( void ) throw () +{ +} + +// ---------------------------------------------------------------------------- + +const char * MutexException::what( void ) const throw () +{ + return m_message; +} + +// ---------------------------------------------------------------------------- + +MutexLocker::MutexLocker( volatile LevelMutexInfo & mutex, bool lock ) : + m_locked( false ), + m_mutex( mutex ) +{ + assert( nullptr != this ); + if ( !lock ) + return; + const MutexErrors::Type result = mutex.Lock(); + m_locked = ( MutexErrors::Success == result ); + if ( !m_locked ) + throw MutexException( "Unable to lock mutex.", mutex.GetLevel(), result ); +} + +// ---------------------------------------------------------------------------- + +MutexLocker::MutexLocker( volatile LevelMutexInfo & mutex, unsigned int milliSeconds, + bool lock ) : + m_locked( false ), + m_mutex( mutex ) +{ + assert( nullptr != this ); + if ( !lock ) + return; + const MutexErrors::Type result = mutex.Lock( milliSeconds ); + m_locked = ( MutexErrors::Success == result ); + if ( !m_locked ) + throw MutexException( "Unable to lock mutex.", mutex.GetLevel(), result ); +} + +// ---------------------------------------------------------------------------- + +MutexLocker::~MutexLocker( void ) +{ + assert( nullptr != this ); + if ( !m_locked ) + return; + try + { + m_mutex.Unlock(); + } + catch ( ... ) + { + // Not much we can do when catching an exception inside a destructor. + } +} + +// ---------------------------------------------------------------------------- + +bool MutexLocker::Lock( void ) +{ + assert( nullptr != this ); + if ( m_locked ) + return true; + const MutexErrors::Type result = m_mutex.Lock(); + if ( MutexErrors::Success != result ) + return false; + m_locked = true; + return true; +} + +// ---------------------------------------------------------------------------- + +bool MutexLocker::Unlock( void ) +{ + assert( nullptr != this ); + if ( !m_locked ) + return true; + const MutexErrors::Type result = m_mutex.Unlock(); + if ( MutexErrors::Success != result ) + return false; + m_locked = false; + return true; +} + +// ---------------------------------------------------------------------------- + +MultiMutexLocker::MultiMutexLocker( LevelMutexInfo::MutexContainer & mutexes, + bool lock ) : + m_locked( false ), + m_mutexes( mutexes ) +{ + assert( nullptr != this ); + if ( !lock ) + return; + const MutexErrors::Type result = LevelMutexInfo::MultiLock( mutexes ); + m_locked = ( MutexErrors::Success == result ); + if ( !m_locked ) + throw MutexException( "Unable to lock multiple mutexes.", + GetLevel( mutexes ), result ); +} + +// ---------------------------------------------------------------------------- + +MultiMutexLocker::MultiMutexLocker( LevelMutexInfo::MutexContainer & mutexes, + unsigned int milliSeconds, bool lock ) : + m_locked( false ), + m_mutexes( mutexes ) +{ + assert( nullptr != this ); + if ( !lock ) + return; + const MutexErrors::Type result = LevelMutexInfo::MultiLock( mutexes, milliSeconds ); + m_locked = ( MutexErrors::Success == result ); + if ( !m_locked ) + throw MutexException( "Unable to lock multiple mutexes.", + GetLevel( mutexes ), result ); +} + +// ---------------------------------------------------------------------------- + +MultiMutexLocker::~MultiMutexLocker( void ) +{ + assert( nullptr != this ); + if ( !m_locked ) + return; + try + { + LevelMutexInfo::MultiUnlock( m_mutexes ); + } + catch ( ... ) + { + // Not much we can do when catching an exception inside a destructor. + } +} + +// ---------------------------------------------------------------------------- + +bool MultiMutexLocker::Lock( void ) +{ + assert( nullptr != this ); + if ( m_locked ) + return true; + const MutexErrors::Type result = LevelMutexInfo::MultiLock( m_mutexes ); + if ( MutexErrors::Success != result ) + return false; + m_locked = true; + return true; +} + +// ---------------------------------------------------------------------------- + +bool MultiMutexLocker::Unlock( void ) +{ + assert( nullptr != this ); + if ( !m_locked ) + return true; + const MutexErrors::Type result = LevelMutexInfo::MultiUnlock( m_mutexes ); + if ( MutexErrors::Success != result ) + return false; + m_locked = false; + return true; +} + +// ---------------------------------------------------------------------------- + +} // end namespace Loki diff --git a/test/ScopeGuard/main.cpp b/test/ScopeGuard/main.cpp index 5f46f1b..b1ffd60 100644 --- a/test/ScopeGuard/main.cpp +++ b/test/ScopeGuard/main.cpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // The Loki Library -// Copyright (c) 2006 Peter Kümmel +// Copyright (c) 2006 Peter K�mmel // Permission to use, copy, modify, distribute and sell this software for any // purpose is hereby granted without fee, provided that the above copyright // notice appear in all copies and that both that copyright notice and this @@ -200,7 +200,13 @@ void User::AddFriendGuardedMacros(User&) &User::CheckIfValid, __FUNCTION__, __LINE__ ); (void)invariantGuard; LOKI_ON_BLOCK_EXIT_OBJ(friends_, &UserCont::pop_back); (void) LOKI_ANONYMOUS_VARIABLE(scopeGuard); + // GCC 4.2 Bug +#if defined(__GNUC__) +#define GCC_VERSION (__GNUC__ * 10 + __GNUC_MINOR__ * 1) +#endif +#if !defined(__GNUC__) || (GCC_VERSION < 42) || (GCC_VERSION > 42) LOKI_ON_BLOCK_EXIT(Decrement, Loki::ByRef(fCount)); (void) LOKI_ANONYMOUS_VARIABLE(scopeGuard); +#endif } void DoStandaloneFunctionTests()