diff options
Diffstat (limited to 'shared/loki/LevelMutex.h')
-rw-r--r-- | shared/loki/LevelMutex.h | 1211 |
1 files changed, 1211 insertions, 0 deletions
diff --git a/shared/loki/LevelMutex.h b/shared/loki/LevelMutex.h new file mode 100644 index 00000000..c048000f --- /dev/null +++ b/shared/loki/LevelMutex.h @@ -0,0 +1,1211 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 <vector> +#include <assert.h> +#include <time.h> + +#if defined( _MSC_VER ) + #include <Windows.h> +#else + #include <pthread.h> +#endif + +#if !defined(_WIN32) && !defined(_WIN64) + #include <unistd.h> // declares sleep under Linux +#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 |