summaryrefslogtreecommitdiff
path: root/shared/loki/LevelMutex.h
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:15:16 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:15:16 +0200
commitbd6336c629841c6db3a6ca53a936d629d34db53b (patch)
tree3721ef997864108df175ce677a8a7d4342a6f1d2 /shared/loki/LevelMutex.h
parent4.0 (diff)
downloadFreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.gz
FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.bz2
FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.zip
4.1
Diffstat (limited to 'shared/loki/LevelMutex.h')
-rw-r--r--shared/loki/LevelMutex.h1211
1 files changed, 0 insertions, 1211 deletions
diff --git a/shared/loki/LevelMutex.h b/shared/loki/LevelMutex.h
deleted file mode 100644
index eb01f44a..00000000
--- a/shared/loki/LevelMutex.h
+++ /dev/null
@@ -1,1211 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-//
-// 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
bgstack15