From 336d434934d603134a4c91c09ca655d2b48efba5 Mon Sep 17 00:00:00 2001 From: rich_sposato Date: Sun, 25 May 2008 04:05:18 +0000 Subject: [PATCH] Adding LevelMutex test program to Loki. git-svn-id: svn://svn.code.sf.net/p/loki-lib/code/trunk@844 7ec92016-0320-0410-acc4-a06ded1c099a --- test/LevelMutex/LevelMutex_MSVC_9.vcproj | 211 ++++ test/LevelMutex/MultiThreadTests.cpp | 1179 ++++++++++++++++++++++ test/LevelMutex/MultiThreadTests.hpp | 55 + test/LevelMutex/Thing.cpp | 922 +++++++++++++++++ test/LevelMutex/Thing.hpp | 364 +++++++ test/LevelMutex/ThreadPool.hpp | 226 +++++ test/LevelMutex/main.cpp | 681 +++++++++++++ 7 files changed, 3638 insertions(+) create mode 100644 test/LevelMutex/LevelMutex_MSVC_9.vcproj create mode 100644 test/LevelMutex/MultiThreadTests.cpp create mode 100644 test/LevelMutex/MultiThreadTests.hpp create mode 100644 test/LevelMutex/Thing.cpp create mode 100644 test/LevelMutex/Thing.hpp create mode 100644 test/LevelMutex/ThreadPool.hpp create mode 100644 test/LevelMutex/main.cpp diff --git a/test/LevelMutex/LevelMutex_MSVC_9.vcproj b/test/LevelMutex/LevelMutex_MSVC_9.vcproj new file mode 100644 index 0000000..61181f9 --- /dev/null +++ b/test/LevelMutex/LevelMutex_MSVC_9.vcproj @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/LevelMutex/MultiThreadTests.cpp b/test/LevelMutex/MultiThreadTests.cpp new file mode 100644 index 0000000..94e124e --- /dev/null +++ b/test/LevelMutex/MultiThreadTests.cpp @@ -0,0 +1,1179 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Part of LevelMutex test program 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$ + + +// ---------------------------------------------------------------------------- + +#include "MultiThreadTests.hpp" + +#include +#include +#include +#include + +#include + +#include "ThreadPool.hpp" +#include "Thing.hpp" + + +// define nullptr even though new compilers will have this keyword just so we +// have a consistent and easy way of identifying which uses of 0 mean null. +#define nullptr 0 + + +using namespace ::Loki; + +static const unsigned int thingCount = 8; + +static bool s_redo = false; + + +// ---------------------------------------------------------------------------- + +void SetToRedoSingleTests( bool redo ) +{ + s_redo = redo; +} + +// ---------------------------------------------------------------------------- + +bool WillRedoSingleTests( void ) +{ + return s_redo; +} + +// ---------------------------------------------------------------------------- + +#if defined(_WIN32) + + typedef unsigned int ( WINAPI * ThreadFunction_ )( void * ); + + #define LOKI_pthread_t HANDLE + + #define LOKI_pthread_create(handle,attr,func,arg) \ + (int)((*handle=(HANDLE) _beginthreadex (nullptr,0,(ThreadFunction_)func,arg,0,nullptr))==nullptr) + + #define LOKI_pthread_join(thread) \ + ((::WaitForSingleObject((thread),INFINITE)!=WAIT_OBJECT_0) || !CloseHandle(thread)) + +#else + + #define LOKI_pthread_t \ + pthread_t + #define LOKI_pthread_create(handle,attr,func,arg) \ + pthread_create(handle,attr,func,arg) + #define LOKI_pthread_join(thread) \ + pthread_join(thread, NULL) + +#endif + +using namespace std; +using namespace Loki; + +// ---------------------------------------------------------------------------- + +//class Thread +//{ +//public: +// +// typedef void * ( * CallFunction )( void * ); +// +// Thread( CallFunction func, void * parm ) +// : pthread_() +// , func_( func ) +// , parm_( parm ) +// { +// } +// +// int Start( void ) +// { +// return LOKI_pthread_create( &pthread_, nullptr, func_, parm_ ); +// } +// +// int WaitForThread( void ) const +// { +// return LOKI_pthread_join( pthread_ ); +// } +// +//private: +// LOKI_pthread_t pthread_; +// CallFunction func_; +// void * parm_; +//}; +// +//// ---------------------------------------------------------------------------- +// +//class ThreadPool +//{ +//public: +// ThreadPool( void ) : m_threads() +// { +// } +// +// void Create( size_t threadCount, Thread::CallFunction function ) +// { +// for( size_t ii = 0; ii < threadCount; ii++ ) +// { +// string buffer; +// SPrintf( buffer, "Creating thread %d\n" )( ii ); +// cout << buffer; +// Thread * thread = new Thread( function, +// reinterpret_cast< void * >( ii ) ); +// m_threads.push_back( thread ); +// } +// } +// +// void Start( void ) +// { +// for ( size_t ii = 0; ii < m_threads.size(); ii++ ) +// { +// m_threads.at( ii )->Start(); +// } +// } +// +// void Join( void ) const +// { +// for ( size_t ii = 0; ii < m_threads.size(); ii++ ) +// m_threads.at( ii )->WaitForThread(); +// } +// +// ~ThreadPool( void ) +// { +// for ( size_t ii = 0; ii < m_threads.size(); ii++ ) +// { +// delete m_threads.at(ii); +// } +// } +// +//private: +// typedef std::vector< Thread * > Threads; +// +// Threads m_threads; +//}; + +// ---------------------------------------------------------------------------- + +void * PrintSafeThread( void * p ) +{ + unsigned int value = reinterpret_cast< unsigned int >( p ); + volatile Thing & thing = Thing::GetIt(); + try + { + for ( unsigned int ii = 0; ii < 5; ++ii ) + { + thing.Print( value, ii, 16 ); + ::GoToSleep( 2 ); + if ( WillRedoSingleTests() ) + { + SingleThreadSimpleTest(); + SingleThreadReentrantTest(); + SingleThreadSimpleMultiLockTest(); + SingleThreadComplexMultiLockTest( false ); + SingleThreadExceptionTest(); + } + } + } + catch ( ... ) + { + assert( false ); + } + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void * PrintUnsafeThread( void * p ) +{ + unsigned int value = reinterpret_cast< unsigned int >( p ); + Thing & thing = const_cast< Thing & >( Thing::GetIt() ); + try + { + for ( unsigned int ii = 0; ii < 5; ++ii ) + { + thing.Print( value, ii, 16 ); + ::GoToSleep( 2 ); + if ( WillRedoSingleTests() ) + { + SingleThreadSimpleTest(); + SingleThreadReentrantTest(); + SingleThreadSimpleMultiLockTest(); + SingleThreadComplexMultiLockTest( false ); + SingleThreadExceptionTest(); + } + } + } + catch ( ... ) + { + assert( false ); + } + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void OutputResults( unsigned int loop, unsigned int value, unsigned int result ) +{ + static volatile SleepMutex mutex( 2 ); + static bool initialized = false; + if ( !initialized ) + { + initialized = true; + #if defined( _MSC_VER ) + mutex.GetMutexPolicy().SetSleepTime( 5 ); + mutex.GetMutexPolicy().SetWakable( true ); + #else + mutex.GetMutexPolicy().SetSleepTime( 1 ); + #endif + } + + MutexLocker locker( mutex ); + (void)locker; + + const char * message = ( result == value ) ? "yes" : "no!"; + Printf( "Loop = %d \t Value = %d \t Result = %d \t Pass? %s \n" ) + ( loop )( value )( result )( message ); +} + +// ---------------------------------------------------------------------------- + +void * ValueSafeThread( void * p ) +{ + + const unsigned int testCount = 8; + unsigned int fails = 0; + const unsigned int value = reinterpret_cast< unsigned int >( p ); + volatile Thing & thing = Thing::GetIt(); + try + { + for ( unsigned int ii = 0; ii < testCount; ++ii ) + { + MutexLocker locker( thing.GetMutex() ); + (void)locker; + thing.SetValue( value ); + ::GoToSleep( 3 ); + const unsigned int result = thing.GetValue(); + OutputResults( ii, value, result ); + if ( result != value ) + fails++; + assert( result == value ); + } + } + catch ( ... ) + { + assert( false ); + } + TestResults::GetIt()->SetResult( value, testCount, fails ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void * ValueUnsafeThread( void * p ) +{ + + const unsigned int testCount = 8; + unsigned int fails = 0; + const unsigned int value = reinterpret_cast< unsigned int >( p ); + // cast away volatility so the mutex doesn't get used by volatile functions. + Thing & thing = const_cast< Thing & >( Thing::GetIt() ); + try + { + for ( unsigned int ii = 0; ii < testCount; ++ii ) + { + thing.SetValue( value ); + ::GoToSleep( 3 ); + const unsigned int result = thing.GetValue(); + OutputResults( ii, value, result ); + if ( result != value ) + fails++; + } + } + catch ( ... ) + { + assert( false ); + } + TestResults::GetIt()->SetResult( value, testCount, fails ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void MultiThreadSimpleTest( void ) +{ + Thing::Init( 0 ); + const unsigned int threadCount = 5; + ThreadPool pool( threadCount ); + + cout << endl << "Doing thread-locked print test. This test should pass. and not deadlock" << endl; + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( PrintSafeThread, p ); + } + pool.JoinAll(); + + cout << endl << "Doing thread-unsafe print test. This test may fail." << endl; + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( PrintUnsafeThread, p ); + } + pool.JoinAll(); + + Thing::Destroy(); +} + +// ---------------------------------------------------------------------------- + +void * TryLockThread( void * p ) +{ + + const unsigned int value = reinterpret_cast< unsigned int >( p ); + volatile Thing & thing = Thing::GetIt(); + volatile SleepMutex & mutex = thing.GetMutex(); + assert( mutex.IsLockedByAnotherThread() ); + + for ( unsigned int ii = 0; ii < 5; ++ii ) + { + MutexErrors::Type result = mutex.TryLock(); + const bool locked = ( result == MutexErrors::Success ); + assert( !locked ); + if ( locked ) + { + // let's pretend we actually locked it, + // and try to change the value. + thing.SetValue( value ); + result = mutex.Unlock(); + assert( result == MutexErrors::Success ); + } + const unsigned int gotValue = thing.GetValue(); + assert( gotValue != value ); + } + assert( mutex.IsLockedByAnotherThread() ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void MultiThreadTryLockTest( void ) +{ + static const unsigned int threadCount = 3; + Thing::Init( 0 ); + volatile Thing & thing = Thing::GetIt(); + volatile SleepMutex & mutex = thing.GetMutex(); + + cout << endl << "Doing multi-threaded TryLock test. This test should not deadlock." << endl; + ::system( "pause" ); + // First step is to lock the mutex in the main thread so no child thread + // can ever lock it, change the value, or anything like that. + MutexErrors::Type result = mutex.Lock(); + assert( MutexErrors::Success == result ); + bool okay = mutex.IsLockedByCurrentThread(); + assert( okay ); + thing.SetValue( threadCount ); + ThreadPool pool( threadCount ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( TryLockThread, p ); + } + pool.JoinAll(); + const unsigned int value = thing.GetValue(); + assert( value == threadCount ); + result = mutex.Unlock(); + assert( MutexErrors::Success == result ); + okay = !mutex.IsLockedByCurrentThread(); + assert( okay ); + okay = !mutex.IsLocked(); + assert( okay ); + + Thing::Destroy(); +} + +// ---------------------------------------------------------------------------- + +void MultiThreadReentrantTest( void ) +{ + Thing::Init( 0 ); + const unsigned int threadCount = 8; + TestResults::Create( threadCount ); + ThreadPool pool( threadCount ); + + cout << endl << "Doing thread-safe value test. This test should pass and not deadlock." << endl; + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( ValueSafeThread, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + cout << endl << "Doing thread-unsafe value test. This test may fail." << endl; + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( ValueUnsafeThread, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + TestResults::Destroy(); + Thing::Destroy(); +} + +// ---------------------------------------------------------------------------- + +bool RandomizeMutexOrder( LevelMutexInfo::MutexContainer & mutexes ) +{ + unsigned int count = mutexes.size(); + if ( count < 2 ) + return false; + + LevelMutexInfo::MutexContainer randomMutexes; + for ( unsigned int ii = 0; ii < count; ++ii ) + { + volatile LevelMutexInfo * mutex = nullptr; + const unsigned int sizeNow = mutexes.size(); + if ( 1 < sizeNow ) + { + unsigned int index = ( ::rand() % sizeNow ); + mutex = mutexes[ index ]; + if ( index < sizeNow - 1 ) + mutexes[ index ] = mutexes[ sizeNow - 1 ]; + } + else + { + mutex = mutexes[ 0 ]; + } + mutexes.pop_back(); + randomMutexes.push_back( mutex ); + } + + mutexes = randomMutexes; + return true; +} + +// ---------------------------------------------------------------------------- + +void SingleThreadComplexMultiLockTest( bool doSetup ) +{ + + ::srand( static_cast< unsigned int >( time( nullptr ) ) ); + if ( doSetup ) + { + Thing::MakePool( thingCount ); + } + + const unsigned int priorLevel = GetCurrentThreadsLevel(); + const unsigned int priorLockCount = CountLocksInCurrentThread(); + const unsigned int priorMutexCount = CountMutexesInCurrentThread(); + const unsigned int priorLevelMutexCount = CountMutexesAtCurrentLevel(); + if ( LevelMutexInfo::UnlockedLevel == priorLevel ) + { + assert( priorLockCount == 0 ); + assert( priorMutexCount == 0 ); + assert( priorLevelMutexCount == 0 ); + } + else + { + assert( priorLockCount != 0 ); + assert( priorMutexCount != 0 ); + assert( priorLevelMutexCount != 0 ); + assert( priorLevelMutexCount <= priorMutexCount ); + assert( priorMutexCount <= priorLockCount ); + } + + volatile Thing * thing0 = Thing::GetFromPool( 0 ); + volatile Thing * thing1 = Thing::GetFromPool( 1 ); + volatile Thing * thing2 = Thing::GetFromPool( 2 ); + volatile Thing * thing3 = Thing::GetFromPool( 3 ); + volatile Thing * thing4 = Thing::GetFromPool( 4 ); + volatile Thing * thing5 = Thing::GetFromPool( 5 ); + volatile Thing * thing6 = Thing::GetFromPool( 6 ); + volatile Thing * thing7 = Thing::GetFromPool( 7 ); + assert( nullptr != thing0 ); + assert( nullptr != thing1 ); + assert( nullptr != thing2 ); + assert( nullptr != thing3 ); + assert( nullptr != thing4 ); + assert( nullptr != thing5 ); + assert( nullptr != thing6 ); + assert( nullptr != thing7 ); + + LevelMutexInfo::MutexContainer mutexes; + mutexes.reserve( thingCount ); + MutexErrors::Type result = LevelMutexInfo::MultiLock( mutexes ); + assert( result == MutexErrors::EmptyContainer ); + + mutexes.push_back( &thing0->GetMutex() ); + mutexes.push_back( &thing1->GetMutex() ); + mutexes.push_back( &thing2->GetMutex() ); + mutexes.push_back( &thing3->GetMutex() ); + mutexes.push_back( &thing4->GetMutex() ); + mutexes.push_back( &thing5->GetMutex() ); + mutexes.push_back( &thing6->GetMutex() ); + mutexes.push_back( &thing7->GetMutex() ); + + result = LevelMutexInfo::MultiLock( mutexes ); + assert( result == MutexErrors::Success ); + assert( GetCurrentThreadsLevel() == thing0->GetMutex().GetLevel() ); + assert( CountLocksInCurrentThread() == priorLockCount + thingCount ); + result = LevelMutexInfo::MultiUnlock( mutexes ); + assert( result == MutexErrors::Success ); + assert( GetCurrentThreadsLevel() == priorLevel ); + assert( CountLocksInCurrentThread() == priorLockCount ); + assert( CountMutexesInCurrentThread() == priorMutexCount ); + assert( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + + mutexes.clear(); + mutexes.push_back( &thing7->GetMutex() ); + mutexes.push_back( &thing6->GetMutex() ); + mutexes.push_back( &thing5->GetMutex() ); + mutexes.push_back( &thing4->GetMutex() ); + mutexes.push_back( &thing3->GetMutex() ); + mutexes.push_back( &thing2->GetMutex() ); + mutexes.push_back( &thing1->GetMutex() ); + mutexes.push_back( &thing0->GetMutex() ); + result = LevelMutexInfo::MultiLock( mutexes ); + assert( result == MutexErrors::Success ); + assert( GetCurrentThreadsLevel() == thing0->GetMutex().GetLevel() ); + assert( CountLocksInCurrentThread() == priorLockCount + thingCount ); + result = LevelMutexInfo::MultiUnlock( mutexes ); + assert( result == MutexErrors::Success ); + assert( GetCurrentThreadsLevel() == priorLevel ); + assert( CountLocksInCurrentThread() == priorLockCount ); + assert( CountMutexesInCurrentThread() == priorMutexCount ); + assert( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + + RandomizeMutexOrder( mutexes ); + result = LevelMutexInfo::MultiLock( mutexes ); + assert( result == MutexErrors::Success ); + assert( GetCurrentThreadsLevel() == thing0->GetMutex().GetLevel() ); + assert( CountLocksInCurrentThread() == priorLockCount + thingCount ); + result = LevelMutexInfo::MultiUnlock( mutexes ); + assert( result == MutexErrors::Success ); + assert( GetCurrentThreadsLevel() == priorLevel ); + assert( CountLocksInCurrentThread() == priorLockCount ); + assert( CountMutexesInCurrentThread() == priorMutexCount ); + assert( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + + if ( doSetup ) + Thing::DestroyPool(); +} + +// ---------------------------------------------------------------------------- + +void * MultiLockSafeThread( void * p ) +{ + + const unsigned int value = reinterpret_cast< unsigned int >( p ); + LevelMutexInfo::MutexContainer mutexes( thingCount ); + volatile Thing * thing = nullptr; + unsigned int jj = 0; + for ( ; jj < thingCount; ++jj ) + { + thing = Thing::GetFromPool( jj ); + assert( nullptr != thing ); + mutexes[ jj ] = &thing->GetMutex(); + } + + unsigned int fails = 0; + unsigned int tests = 0; + unsigned int randomIndex = 0; + try + { + for ( unsigned int ii = 0; ii < thingCount; ++ii ) + { + MultiMutexLocker locker( mutexes ); + (void)locker; + + for ( jj = 0; jj < thingCount; ++jj ) + { + thing = Thing::GetFromPool( jj ); + assert( nullptr != thing ); + thing->SetValue( value ); + } + ::GoToSleep( 2 ); + + if ( WillRedoSingleTests() ) + { + SingleThreadSimpleTest(); + SingleThreadReentrantTest(); + SingleThreadSimpleMultiLockTest(); + SingleThreadComplexMultiLockTest( false ); + SingleThreadExceptionTest(); + } + + thing = Thing::GetFromPool( ii ); + assert( nullptr != thing ); + thing->Print( value, ii, 7 ); + randomIndex = ( ::rand() % thingCount ); + thing = Thing::GetFromPool( randomIndex ); + assert( nullptr != thing ); + thing->Print( value, ii + 10, 7 ); + + for ( jj = 0; jj < thingCount; ++jj ) + { + thing = Thing::GetFromPool( jj ); + assert( nullptr != thing ); + if ( thing->GetValue() != value ) + fails++; + tests++; + } + + bool okay = locker.Unlock(); + assert( okay ); + ::GoToSleep( 2 ); + } + } + catch ( ... ) + { + assert( false ); + } + + TestResults::GetIt()->SetResult( value, tests, fails ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void * MultiLockUnsafeThread( void * p ) +{ + + const unsigned int value = reinterpret_cast< unsigned int >( p ); + Thing * thing = nullptr; + unsigned int jj = 0; + unsigned int tests = 0; + unsigned int fails = 0; + unsigned int randomIndex = 0; + try + { + for ( unsigned int ii = 0; ii < thingCount; ++ii ) + { + for ( jj = 0; jj < thingCount; ++jj ) + { + thing = const_cast< Thing * >( Thing::GetFromPool( jj ) ); + assert( nullptr != thing ); + thing->SetValue( value ); + } + ::GoToSleep( 2 ); + + if ( WillRedoSingleTests() ) + { + SingleThreadSimpleTest(); + SingleThreadReentrantTest(); + SingleThreadSimpleMultiLockTest(); + SingleThreadComplexMultiLockTest( false ); + SingleThreadExceptionTest(); + } + + thing = const_cast< Thing * >( Thing::GetFromPool( ii ) ); + assert( nullptr != thing ); + thing->Print( value, ii, 7 ); + randomIndex = ( ::rand() % thingCount ); + thing = const_cast< Thing * >( Thing::GetFromPool( randomIndex ) ); + assert( nullptr != thing ); + thing->Print( value, ii, 7 ); + + for ( jj = 0; jj < thingCount; ++jj ) + { + thing = const_cast< Thing * >( Thing::GetFromPool( jj ) ); + assert( nullptr != thing ); + if ( thing->GetValue() != value ) + fails++; + tests++; + } + + ::GoToSleep( 2 ); + } + } + catch ( ... ) + { + assert( false ); + } + + TestResults::GetIt()->SetResult( value, tests, fails ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void MultiThreadMultiLockTest( void ) +{ + Thing::MakePool( thingCount ); + const unsigned int threadCount = 8; + TestResults::Create( threadCount ); + ThreadPool pool( threadCount ); + + cout << endl << "Doing thread-safe multi-lock test. This test should pass and not deadlock." << endl; + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( MultiLockSafeThread, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + cout << endl << "Doing thread-unsafe multi-lock test. This test may fail." << endl; + TestResults::GetIt()->Reset( threadCount ); + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( MultiLockUnsafeThread, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + TestResults::Destroy(); + Thing::DestroyPool(); +} + +// ---------------------------------------------------------------------------- + +void * MultiLockRandomSafeThread( void * p ) +{ + const unsigned int value = reinterpret_cast< unsigned int >( p ); + unsigned int testCount = 0; + unsigned int failCount = 0; + volatile Thing * thing = nullptr; + Thing::ThingPool pool; + LevelMutexInfo::MutexContainer mutexes; + mutexes.reserve( thingCount ); + + unsigned int jj = 0; + unsigned int place = 0; + try + { + for ( unsigned int ii = 0; ii < thingCount; ++ii ) + { + + pool.clear(); + pool.reserve( thingCount ); + for ( place = 0; place < thingCount; ++place ) + { + place += ::rand() % 3; + if ( thingCount <= place ) + break; + thing = Thing::GetFromPool( place ); + assert( nullptr != thing ); + pool.push_back( thing ); + } + const unsigned int poolCount = pool.size(); + + mutexes.clear(); + for ( jj = 0; jj < poolCount; ++jj ) + { + thing = pool[ jj ]; + assert( nullptr != thing ); + mutexes.push_back( &thing->GetMutex() ); + } + + MultiMutexLocker locker( mutexes ); + (void)locker; + for ( jj = 0; jj < poolCount; ++jj ) + { + thing = pool[ jj ]; + assert( nullptr != thing ); + thing->SetValue( value ); + } + ::GoToSleep( 3 ); + + for ( jj = 0; jj < poolCount; ++jj ) + { + thing = pool[ jj ]; + assert( nullptr != thing ); + if ( thing->GetValue() != value ) + failCount++; + testCount++; + } + } + } + catch ( ... ) + { + assert( false ); + } + + TestResults::GetIt()->SetResult( value, testCount, failCount ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void * MultiLockRandomUnsafeThread( void * p ) +{ + const unsigned int value = reinterpret_cast< unsigned int >( p ); + unsigned int testCount = 0; + unsigned int failCount = 0; + Thing * thing = nullptr; + UnsafeThingPool pool; + + unsigned int jj = 0; + unsigned int place = 0; + try + { + for ( unsigned int ii = 0; ii < thingCount; ++ii ) + { + + pool.clear(); + pool.reserve( thingCount ); + for ( place = 0; place < thingCount; ++place ) + { + place += ::rand() % 3; + if ( thingCount <= place ) + break; + thing = thing = const_cast< Thing * >( Thing::GetFromPool( place ) ); + assert( nullptr != thing ); + pool.push_back( thing ); + } + const unsigned int poolCount = pool.size(); + + for ( jj = 0; jj < poolCount; ++jj ) + { + thing = pool[ jj ]; + assert( nullptr != thing ); + thing->SetValue( value ); + } + ::GoToSleep( 3 ); + + for ( jj = 0; jj < poolCount; ++jj ) + { + thing = pool[ jj ]; + assert( nullptr != thing ); + if ( thing->GetValue() != value ) + failCount++; + testCount++; + } + } + } + catch ( ... ) + { + assert( false ); + } + + TestResults::GetIt()->SetResult( value, testCount, failCount ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void MultiThreadRandomMultiLockTest( void ) +{ + Thing::MakePool( thingCount ); + const unsigned int threadCount = 8; + TestResults::Create( threadCount ); + ThreadPool pool( threadCount ); + + cout << endl << "Doing thread-safe random multi-lock test. This test should pass and not deadlock." << endl; + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( MultiLockRandomSafeThread, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + cout << endl << "Doing thread-unsafe random multi-lock test. This test may fail." << endl; + TestResults::GetIt()->Reset( threadCount ); + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( MultiLockRandomUnsafeThread, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + TestResults::Destroy(); + Thing::DestroyPool(); +} + +// ---------------------------------------------------------------------------- + +void * SafeHierarchyTest( void * p ) +{ + + const unsigned int value = reinterpret_cast< unsigned int >( p ); + volatile LevelThing * thing = nullptr; + unsigned int testCount = 0; + unsigned int failCount = 0; + + try + { + for ( unsigned int ii = 0; ii < thingCount; ++ii ) + { + { + thing = LevelThing::GetFromPool( ii ); + assert( nullptr != thing ); + LevelThing::Unlocker unlocker( thing->LockHierarchy() ); + (void)unlocker; + thing->SetValue( value ); + ::GoToSleep( 3 ); + if ( !thing->DoValuesMatch( value ) ) + failCount++; + testCount++; + } + { + const unsigned int randomIndex = ( ::rand() % thingCount ); + thing = LevelThing::GetFromPool( randomIndex ); + assert( nullptr != thing ); + LevelThing::Unlocker unlocker( thing->LockHierarchy() ); + (void)unlocker; + thing->SetValue( value ); + ::GoToSleep( 3 ); + if ( !thing->DoValuesMatch( value ) ) + failCount++; + testCount++; + } + } + } + catch ( ... ) + { + assert( false ); + } + + TestResults::GetIt()->SetResult( value, testCount, failCount ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void * UnsafeHierarchyTest( void * p ) +{ + + const unsigned int value = reinterpret_cast< unsigned int >( p ); + LevelThing * thing = nullptr; + unsigned int testCount = 0; + unsigned int failCount = 0; + + try + { + for ( unsigned int ii = 0; ii < thingCount; ++ii ) + { + { + thing = const_cast< LevelThing * >( LevelThing::GetFromPool( ii ) ); + assert( nullptr != thing ); + thing->SetValue( value ); + ::GoToSleep( 3 ); + if ( !thing->DoValuesMatch( value ) ) + failCount++; + testCount++; + } + { + const unsigned int randomIndex = ( ::rand() % thingCount ); + thing = const_cast< LevelThing * >( LevelThing::GetFromPool( randomIndex ) ); + assert( nullptr != thing ); + thing->SetValue( value ); + ::GoToSleep( 3 ); + if ( !thing->DoValuesMatch( value ) ) + failCount++; + testCount++; + } + } + } + catch ( ... ) + { + assert( false ); + } + + TestResults::GetIt()->SetResult( value, testCount, failCount ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void MultiThreadHierarchySingleLockTest( void ) +{ + LevelThing::MakePool( thingCount ); + const unsigned int threadCount = 8; + TestResults::Create( threadCount ); + ThreadPool pool( threadCount ); + + cout << endl << "Doing thread-safe hierarchy test. This test should pass and not deadlock." << endl; + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( SafeHierarchyTest, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + cout << endl << "Doing thread-unsafe hierarchy test. This test may fail." << endl; + ::system( "pause" ); + TestResults::GetIt()->Reset( threadCount ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( UnsafeHierarchyTest, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + TestResults::Destroy(); + LevelThing::DestroyPool(); +} + +// ---------------------------------------------------------------------------- + +void * SafeHierarchyMultiLockTest( void * p ) +{ + + const unsigned int value = reinterpret_cast< unsigned int >( p ); + unsigned int testCount = 0; + unsigned int failCount = 0; + unsigned int totalTestCount = 0; + unsigned int totalFailCount = 0; + + try + { + for ( unsigned int ii = 0; ii < 20; ++ii ) + { + SomeThingPool pool; + MakePool( pool ); + assert( CountLockedByThisThread( pool ) == 0 ); + LockThese( pool ); + assert( CountLockedByThisThread( pool ) == pool.size() ); + + for ( SomeThingPoolIter it( pool.begin() ); + it != pool.end(); + ++it ) + { + volatile SomeThing * thing = *it; + assert( nullptr != thing ); + thing->SetValue( value ); + } + + ::GoToSleep( 2 ); + testCount = 0; + failCount = 0; + CheckForMatchingValues( failCount, testCount, value, pool, true ); + totalTestCount += testCount; + totalFailCount += failCount; + + UnlockThese( pool ); + assert( CountLockedByThisThread( pool ) == 0 ); + } + } + catch ( ... ) + { + assert( false ); + } + + TestResults::GetIt()->SetResult( value, totalTestCount, totalFailCount ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void * UnsafeHierarchyMultiLockTest( void * p ) +{ + + const unsigned int value = reinterpret_cast< unsigned int >( p ); + unsigned int testCount = 0; + unsigned int failCount = 0; + unsigned int totalTestCount = 0; + unsigned int totalFailCount = 0; + + try + { + for ( unsigned int ii = 0; ii < 20; ++ii ) + { + SomeThingPool pool; + MakePool( pool ); + for ( SomeThingPoolIter it( pool.begin() ); + it != pool.end(); + ++it ) + { + SomeThing * thing = const_cast< SomeThing * >( *it ); + assert( nullptr != thing ); + thing->SetValue( value ); + } + ::GoToSleep( 2 ); + testCount = 0; + failCount = 0; + CheckForMatchingValues( failCount, testCount, value, pool ); + totalTestCount += testCount; + totalFailCount += failCount; + } + } + catch ( ... ) + { + assert( false ); + } + + TestResults::GetIt()->SetResult( value, totalTestCount, totalFailCount ); + + return nullptr; +} + +// ---------------------------------------------------------------------------- + +void MultiThreadHierarchyMultiLockTest( void ) +{ + + MultiLevelPool::MakePool( 10, thingCount ); + const unsigned int threadCount = 8; + TestResults::Create( threadCount ); + ThreadPool pool( threadCount ); + + cout << endl << "Doing thread-safe multilock hierarchy test. This test should pass and not deadlock." << endl; + ::system( "pause" ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( SafeHierarchyMultiLockTest, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + cout << endl << "Doing thread-unsafe multilock hierarchy test. This test may fail." << endl; + ::system( "pause" ); + TestResults::GetIt()->Reset( threadCount ); + for ( unsigned int ii = 0; ii < threadCount; ++ii ) + { + void * p = reinterpret_cast< void * >( ii ); + pool.Start( UnsafeHierarchyMultiLockTest, p ); + } + pool.JoinAll(); + TestResults::GetIt()->OutputResults(); + + TestResults::Destroy(); + MultiLevelPool::DestroyPool(); +} + +// ---------------------------------------------------------------------------- diff --git a/test/LevelMutex/MultiThreadTests.hpp b/test/LevelMutex/MultiThreadTests.hpp new file mode 100644 index 0000000..42cd621 --- /dev/null +++ b/test/LevelMutex/MultiThreadTests.hpp @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Part of LevelMutex test program 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$ + + +#ifndef LOKI_MULTI_THREAD_TESTS_H_INCLUDED +#define LOKI_MULTI_THREAD_TESTS_H_INCLUDED + +// ---------------------------------------------------------------------------- + +void SetToRedoSingleTests( bool redo ); + +bool WillRedoSingleTests( void ); + +void SingleThreadSimpleTest( void ); + +void SingleThreadReentrantTest( void ); + +void SingleThreadSimpleMultiLockTest( void ); + +void SingleThreadComplexMultiLockTest( bool doSetup ); + +void SingleThreadExceptionTest( void ); + +void MultiThreadSimpleTest( void ); + +void MultiThreadTryLockTest( void ); + +void MultiThreadReentrantTest( void ); + +void MultiThreadMultiLockTest( void ); + +void MultiThreadRandomMultiLockTest( void ); + +void MultiThreadHierarchySingleLockTest( void ); + +void MultiThreadHierarchyMultiLockTest( void ); + +// ---------------------------------------------------------------------------- + +#endif diff --git a/test/LevelMutex/Thing.cpp b/test/LevelMutex/Thing.cpp new file mode 100644 index 0000000..7c34cfd --- /dev/null +++ b/test/LevelMutex/Thing.cpp @@ -0,0 +1,922 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Part of LevelMutex test program 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$ + + +// ---------------------------------------------------------------------------- + +#include "Thing.hpp" + +#include +#include +#include + +#include +#include + +using namespace ::Loki; + + +// ---------------------------------------------------------------------------- + +typedef ::Loki::LockingPtr< Thing, volatile SleepMutex > SingleThingLocker; + +typedef ::Loki::LockingPtr< const Thing, volatile SleepMutex > ConstSingleThingLocker; + +typedef ::Loki::LockingPtr< LevelThing, volatile SleepMutex > LevelThingLocker; + +typedef ::Loki::LockingPtr< const LevelThing, volatile SleepMutex > ConstLevelThingLocker; + +typedef ::Loki::LockingPtr< SomeThing, volatile SleepMutex > SomeThingLocker; + +typedef ::Loki::LockingPtr< const SomeThing, volatile SleepMutex > ConstSomeThingLocker; + +volatile Thing * Thing::s_thing = NULL; + +Thing::ThingPool Thing::s_pool; + +LevelThing::LevelThingPool LevelThing::s_pool; + +TestResults * TestResults::s_instance = NULL; + +MultiLevelPool::MultiThingPool MultiLevelPool::s_pool; + + +// ---------------------------------------------------------------------------- + +ExceptionTossingMutex::ExceptionTossingMutex( unsigned int level ) : +#if defined( _MSC_VER ) + SleepLevelMutex( level ), +#else + SleepLevelMutex( level ), +#endif + m_policy( ExceptionTossingMutex::Never ) +{ + assert( NULL != this ); +#if defined( _MSC_VER ) + SetSleepTime( 5 ); + SetWakable( false ); +#else + SetSleepTime( 1 ); +#endif +} + +// ---------------------------------------------------------------------------- + +ExceptionTossingMutex::~ExceptionTossingMutex( void ) +{ + assert( NULL != this ); +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type ExceptionTossingMutex::Lock( void ) volatile +{ + if ( m_policy != ExceptionTossingMutex::Never ) + { + bool toss = ( m_policy == ExceptionTossingMutex::Always ); + if ( !toss ) + { + const unsigned int number = ( ::rand() % 16 ); + toss = ( 0 == number ); + } + if ( toss ) + { + throw MutexException( "Just a test exception from DoLock.", + GetLevel(), ::Loki::MutexErrors::OtherError ); + } + } + return SleepLevelMutex::Lock(); +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type ExceptionTossingMutex::TryLock( void ) volatile +{ + if ( m_policy != ExceptionTossingMutex::Never ) + { + bool toss = ( m_policy == ExceptionTossingMutex::Always ); + if ( !toss ) + { + const unsigned int number = ( ::rand() % 16 ); + toss = ( 0 == number ); + } + if ( toss ) + { + throw MutexException( "Just a test exception from DoLock.", + GetLevel(), ::Loki::MutexErrors::OtherError ); + } + } + return SleepLevelMutex::TryLock(); +} + +// ---------------------------------------------------------------------------- + +MutexErrors::Type ExceptionTossingMutex::Unlock( void ) volatile +{ + if ( m_policy != ExceptionTossingMutex::Never ) + { + bool toss = ( m_policy == ExceptionTossingMutex::Always ); + if ( !toss ) + { + const unsigned int number = ( ::rand() % 16 ); + toss = ( 0 == number ); + } + if ( toss ) + { + throw MutexException( "Just a test exception from DoLock.", + GetLevel(), ::Loki::MutexErrors::OtherError ); + } + } + return SleepLevelMutex::Unlock(); +} + +// ---------------------------------------------------------------------------- + +bool TestResults::Create( unsigned int threadCount ) +{ + if ( NULL != s_instance ) + return true; + s_instance = new TestResults( threadCount ); + return ( NULL != s_instance ); +} + +// ---------------------------------------------------------------------------- + +void TestResults::Destroy( void ) +{ + delete s_instance; + s_instance = NULL; +} + +// ---------------------------------------------------------------------------- + +TestResults::TestResults( unsigned int threadCount ) : + m_results() +{ + assert( NULL != this ); + Reset( threadCount ); +} + +// ---------------------------------------------------------------------------- + +TestResults::~TestResults( void ) +{ + assert( NULL != this ); + assert( this == s_instance ); +} + +// ---------------------------------------------------------------------------- + +void TestResults::Reset( unsigned int threadCount ) +{ + assert( NULL != this ); + TestResult result; + result.m_total = 0; + result.m_fails = 0; + m_results.clear(); + m_results.resize( threadCount, result ); +} + +// ---------------------------------------------------------------------------- + +void TestResults::SetResult( unsigned int threadIndex, unsigned int total, + unsigned int fails ) +{ + assert( NULL != this ); + assert( this == s_instance ); + + if ( threadIndex <= m_results.size() ) + { + m_results[ threadIndex ].m_total = total; + m_results[ threadIndex ].m_fails = fails; + } +} + +// ---------------------------------------------------------------------------- + +void TestResults::OutputResults( void ) +{ + assert( NULL != this ); + assert( this == s_instance ); + + bool passed = true; + const unsigned int count = m_results.size(); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + Printf( "Thread: %d \t total: %d \t fails: %d\n" )( ii ) + ( m_results[ ii ].m_total ) + ( m_results[ ii ].m_fails ); + if ( m_results[ ii ].m_fails != 0 ) + passed = false; + m_results[ ii ].m_total = 0; + m_results[ ii ].m_fails = 0; + } + const char * message = ( passed ) ? "Passed!" : "FAILED!"; + Printf( "Value Test Result: %s\n" )( message ); +} + +// ---------------------------------------------------------------------------- + +TestResults::TestResult::TestResult( void ) : + m_total( 0 ), + m_fails( 0 ) +{ + assert( NULL != this ); +} + +// ---------------------------------------------------------------------------- + +TestResults::TestResult::TestResult( const TestResult & that ) : + m_total( that.m_total ), + m_fails( that.m_fails ) +{ + assert( NULL != this ); +} + +// ---------------------------------------------------------------------------- + +TestResults::TestResult::~TestResult( void ) +{ + assert( NULL != this ); +} + +// ---------------------------------------------------------------------------- + +void GoToSleep( unsigned int milliSeconds ) +{ +#if defined( _MSC_VER ) + ::SleepEx( milliSeconds, true ); +#elif ( __GNUC__ ) + unsigned int seconds = milliSeconds / 1000; + if ( 0 == seconds ) + seconds = 1; + ::_sleep( seconds ); +#else + #error "Find out if your compiler supports a sleep command and add it here." +#endif +} + +// ---------------------------------------------------------------------------- + +void Thing::Init( unsigned int value ) +{ + if ( NULL != s_thing ) + return; + s_thing = new Thing( value ); +} + +// ---------------------------------------------------------------------------- + +void Thing::Destroy( void ) +{ + if ( NULL == s_thing ) + return; + delete s_thing; + s_thing = NULL; +} + +// ---------------------------------------------------------------------------- + +volatile Thing * Thing::GetFromPool( unsigned int index ) +{ + if ( s_pool.size() <= index ) + return NULL; + volatile Thing * thing = s_pool[ index ]; + return thing; +} + +// ---------------------------------------------------------------------------- + +unsigned int Thing::GetPoolSize( void ) +{ + return s_pool.size(); +} + +// ---------------------------------------------------------------------------- + +bool Thing::MakePool( unsigned int count ) +{ + if ( ( 0 == count ) || ( 100 < count ) ) + return false; + + DestroyPool(); + s_pool.reserve( count ); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + volatile Thing * thing = new Thing( ii ); + s_pool.push_back( thing ); + } + + return true; +} + +// ---------------------------------------------------------------------------- + +void Thing::DestroyPool( void ) +{ + const unsigned int count = s_pool.size(); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + volatile Thing * thing = s_pool[ ii ]; + delete thing; + } + s_pool.clear(); +} + +// ---------------------------------------------------------------------------- + +Thing::Thing( unsigned int value ) : + m_mutex( 10 ), + m_value( value ) +{ + assert( NULL != this ); +#if defined( _MSC_VER ) + m_mutex.GetMutexPolicy().SetSleepTime( 5 ); + m_mutex.GetMutexPolicy().SetWakable( false ); +#else + m_mutex.GetMutexPolicy().SetSleepTime( 1 ); +#endif +} + +// ---------------------------------------------------------------------------- + +Thing::~Thing( void ) +{ + assert( NULL != this ); +} + +// ---------------------------------------------------------------------------- + +void Thing::Print( unsigned int value, unsigned int index, unsigned int startSize ) const volatile +{ + assert( NULL != this ); + volatile SleepMutex & mutex = const_cast< volatile SleepMutex & >( m_mutex ); + ConstSingleThingLocker pSafeThis( *this, mutex ); + pSafeThis->Print( value, index, startSize ); +} + +// ---------------------------------------------------------------------------- + +void Thing::Print( unsigned int value, unsigned int index, unsigned int startSize ) const +{ + assert( NULL != this ); + switch ( startSize ) + { + default: + case 16: ::GoToSleep( 3 ); Printf("%d: %d: ----------------\n")( value )( index ); + case 15: ::GoToSleep( 3 ); Printf("%d: %d: ---------------\n")( value )( index ); + case 14: ::GoToSleep( 3 ); Printf("%d: %d: --------------\n")( value )( index ); + case 13: ::GoToSleep( 3 ); Printf("%d: %d: -------------\n")( value )( index ); + case 12: ::GoToSleep( 3 ); Printf("%d: %d: ------------\n")( value )( index ); + case 11: ::GoToSleep( 3 ); Printf("%d: %d: -----------\n")( value )( index ); + case 10: ::GoToSleep( 3 ); Printf("%d: %d: ----------\n")( value )( index ); + case 9: ::GoToSleep( 3 ); Printf("%d: %d: ---------\n")( value )( index ); + case 8: ::GoToSleep( 3 ); Printf("%d: %d: --------\n")( value )( index ); + case 7: ::GoToSleep( 3 ); Printf("%d: %d: -------\n")( value )( index ); + case 6: ::GoToSleep( 3 ); Printf("%d: %d: ------\n")( value )( index ); + case 5: ::GoToSleep( 3 ); Printf("%d: %d: -----\n")( value )( index ); + case 4: ::GoToSleep( 3 ); Printf("%d: %d: ----\n")( value )( index ); + case 3: ::GoToSleep( 3 ); Printf("%d: %d: ---\n")( value )( index ); + case 2: ::GoToSleep( 3 ); Printf("%d: %d: --\n")( value )( index ); + case 1: ::GoToSleep( 3 ); Printf("%d: %d: -\n")( value )( index ); + case 0: ::GoToSleep( 3 ); Printf("%d: %d: \n")( value )( index ); + } +} + +// ---------------------------------------------------------------------------- + +void Thing::SetValue( unsigned int value ) volatile +{ + assert( NULL != this ); + SingleThingLocker pSafeThis( *this, m_mutex ); + pSafeThis->m_value = value; +} + +// ---------------------------------------------------------------------------- + +volatile LevelThing * LevelThing::GetFromPool( unsigned int index ) +{ + if ( s_pool.size() <= index ) + return NULL; + volatile LevelThing * thing = s_pool[ index ]; + return thing; +} + +// ---------------------------------------------------------------------------- + +bool LevelThing::MakePool( unsigned int count ) +{ + s_pool.clear(); + s_pool.reserve( count ); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + volatile LevelThing * thing = new LevelThing( ii * 10, ii ); + s_pool.push_back( thing ); + } + return true; +} + +// ---------------------------------------------------------------------------- + +void LevelThing::DestroyPool( void ) +{ + const unsigned int count = s_pool.size(); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + volatile LevelThing * thing = s_pool[ ii ]; + delete thing; + } + s_pool.clear(); +} + +// ---------------------------------------------------------------------------- + +LevelThing::LevelThing( unsigned int level, unsigned int place ) : + m_mutex( level ), + m_place( place ), + m_value( 0 ) +{ + assert( NULL != this ); +#if defined( _MSC_VER ) + m_mutex.GetMutexPolicy().SetSleepTime( 5 ); + m_mutex.GetMutexPolicy().SetWakable( false ); +#else + m_mutex.GetMutexPolicy().SetSleepTime( 1 ); +#endif +} + +// ---------------------------------------------------------------------------- + +LevelThing::~LevelThing( void ) +{ + assert( NULL != this ); +} + +// ---------------------------------------------------------------------------- + +LevelThing::Unlocker LevelThing::LockHierarchy( void ) volatile +{ + assert( NULL != this ); + for ( signed ii = m_place; 0 <= ii; --ii ) + { + volatile LevelThing * thing = s_pool[ ii ]; + assert( NULL != thing ); + const MutexErrors::Type result = thing->m_mutex.Lock(); + if ( MutexErrors::Success != result ) + break; + } + + Unlocker unlocker( this ); + return unlocker; +} + +// ---------------------------------------------------------------------------- + +void LevelThing::UnlockHierarchy( void ) volatile +{ + assert( NULL != this ); + try + { + for ( unsigned ii = 0; ii <= m_place; ++ii ) + { + volatile LevelThing * thing = s_pool[ ii ]; + assert( NULL != thing ); + thing->m_mutex.Unlock(); + } + } + catch ( ... ) + { + } +} + +// ---------------------------------------------------------------------------- + +void LevelThing::SetValue( unsigned int value ) volatile +{ + assert( NULL != this ); + MutexLocker locker( m_mutex, !m_mutex.IsLockedByCurrentThread() ); + (void)locker; + m_value = value; + if ( 0 == m_place ) + return; + + volatile LevelThing * thing = s_pool[ m_place - 1 ]; + assert( NULL != thing ); + thing->SetValue( value ); +} + +// ---------------------------------------------------------------------------- + +void LevelThing::SetValue( unsigned int value ) +{ + assert( NULL != this ); + m_value = value; + if ( 0 == m_place ) + return; + + LevelThing * thing = const_cast< LevelThing * >( s_pool[ m_place - 1 ] ); + assert( NULL != thing ); + thing->SetValue( value ); +} + +// ---------------------------------------------------------------------------- + +bool LevelThing::DoValuesMatch( unsigned int value ) const volatile +{ + assert( NULL != this ); + { + MutexLocker locker( m_mutex, !m_mutex.IsLockedByCurrentThread() ); + (void)locker; + if ( m_value != value ) + return false; + } + if ( 0 == m_place ) + return true; + + const volatile LevelThing * thing = s_pool[ m_place - 1 ]; + assert( NULL != thing ); + return thing->DoValuesMatch( value ); +} + +// ---------------------------------------------------------------------------- + +bool LevelThing::DoValuesMatch( unsigned int value ) const +{ + assert( NULL != this ); + if ( m_value != value ) + return false; + if ( 0 == m_place ) + return true; + + const LevelThing * thing = const_cast< LevelThing * >( s_pool[ m_place - 1 ] ); + assert( NULL != thing ); + return thing->DoValuesMatch( value ); +} + +// ---------------------------------------------------------------------------- + +SomeThing::SomeThing( unsigned int level, unsigned int place ) : + m_mutex( level ), + m_place( place ), + m_level( level ), + m_value( 0 ) +{ + assert( NULL != this ); +#if defined( _MSC_VER ) + m_mutex.GetMutexPolicy().SetSleepTime( 5 ); + m_mutex.GetMutexPolicy().SetWakable( false ); +#else + m_mutex.GetMutexPolicy().SetSleepTime( 1 ); +#endif +} + +// ---------------------------------------------------------------------------- + +SomeThing::~SomeThing( void ) +{ + assert( NULL != this ); +} + +// ---------------------------------------------------------------------------- + +void SomeThing::SetValue( unsigned int value ) volatile +{ + assert( NULL != this ); + SomeThingLocker pSafeThis( *this, m_mutex ); + pSafeThis->m_value = value; +} + +// ---------------------------------------------------------------------------- + +void SomeThing::SetValue( unsigned int value ) +{ + assert( NULL != this ); + m_value = value; +} + +// ---------------------------------------------------------------------------- + +ManyThingsPool::ManyThingsPool( unsigned int level, unsigned int count ) : + m_pool() +{ + assert( NULL != this ); + m_pool.reserve( count ); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + volatile SomeThing * thing = new SomeThing( level, ii ); + m_pool.push_back( thing ); + } +} + +// ---------------------------------------------------------------------------- + +ManyThingsPool::~ManyThingsPool( void ) +{ + assert( NULL != this ); + const unsigned int count = m_pool.size(); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + volatile SomeThing * thing = m_pool[ ii ]; + delete thing; + } + m_pool.clear(); +} + +// ---------------------------------------------------------------------------- + +unsigned int ManyThingsPool::GetCount( void ) const +{ + assert( NULL != this ); + const unsigned int count = m_pool.size(); + return count; +} + +// ---------------------------------------------------------------------------- + +volatile SomeThing * ManyThingsPool::GetFromPool( unsigned int index ) +{ + assert( NULL != this ); + ManyThingsPool * pThis = const_cast< ManyThingsPool * >( this ); + if ( pThis->m_pool.size() <= index ) + return NULL; + volatile SomeThing * thing = pThis->m_pool[ index ]; + assert( NULL != thing ); + return thing; +} + +// ---------------------------------------------------------------------------- + +void MultiLevelPool::MakePool( unsigned int count, unsigned int thingCount ) +{ + s_pool.clear(); + s_pool.reserve( count ); + unsigned int level = 100; + for ( unsigned int ii = 0; ii < count; ++ii ) + { + const unsigned int newCount = ( ::rand() % thingCount ) + 4; + ManyThingsPool * pool = new ManyThingsPool( ii + level, newCount ); + s_pool.push_back( pool ); + } +} + +// ---------------------------------------------------------------------------- + +void MultiLevelPool::DestroyPool( void ) +{ + const unsigned int count = s_pool.size(); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + ManyThingsPool * pool = s_pool[ ii ]; + delete pool; + } + s_pool.clear(); +} + +// ---------------------------------------------------------------------------- + +unsigned int MultiLevelPool::GetCount( void ) +{ + const unsigned int count = s_pool.size(); + return count; +} + +// ---------------------------------------------------------------------------- + +ManyThingsPool * MultiLevelPool::GetFromPool( unsigned int index ) +{ + if ( s_pool.size() <= index ) + return NULL; + ManyThingsPool * pool = s_pool[ index ]; + assert( NULL != pool ); + return pool; +} + +// ---------------------------------------------------------------------------- + +void CheckForMatchingValues( unsigned int & failCount, unsigned int & testCount, + unsigned int value, const SomeThingPool & pool ) +{ + const unsigned int count = pool.size(); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + SomeThing * thing = const_cast< SomeThing * >( pool[ ii ] ); + assert( NULL != thing ); + testCount++; + if ( value != thing->GetValue() ) + failCount++; + } +} + +// ---------------------------------------------------------------------------- + +void CheckForMatchingValues( unsigned int & failCount, unsigned int & testCount, + unsigned int value, const SomeThingPool & pool, bool locked ) +{ + if ( !locked ) + { + CheckForMatchingValues( failCount, testCount, value, pool ); + return; + } + + const unsigned int count = pool.size(); + for ( unsigned int ii = 0; ii < count; ++ii ) + { + volatile SomeThing * thing = pool[ ii ]; + assert( NULL != thing ); + assert( thing->GetMutex().IsLockedByCurrentThread() ); + testCount++; + if ( value != thing->GetValue() ) + failCount++; + } +} + +// ---------------------------------------------------------------------------- + +void MakePool( SomeThingPool & target ) +{ + SomeThingPool temp; + bool useThis = false; + unsigned int count2 = 0; + unsigned int r = 0; + bool first = true; + const unsigned int count1 = MultiLevelPool::GetCount(); + + for ( unsigned int index1 = 0; index1 < count1; ++index1 ) + { + r = ( ::rand() % 4 ); + useThis = ( 0 == index1 ) || ( r != 0 ); + if ( !useThis ) + continue; + + ManyThingsPool * pool = MultiLevelPool::GetFromPool( index1 ); + assert( NULL != pool ); + count2 = pool->GetCount(); + first = true; + + for ( unsigned int index2 = 0; index2 < count2; ++index2 ) + { + r = ( ::rand() % 4 ); + useThis = ( 0 == index1 ) || ( r != 0 ); + if ( !useThis ) + continue; + volatile SomeThing * thing = pool->GetFromPool( index2 ); + assert( NULL != thing ); + temp.push_back( thing ); + + if ( first ) + { + // The containers at most levels will have multiple mutexes, + // but some levels will have just a single mutex. + r = ( ::rand() % 4 ); + if ( r != 0 ) + break; + } + first = false; + } + } + + temp.swap( target ); +} + +// ---------------------------------------------------------------------------- + +/// @struct SomeThingComparator Binary-functor for comparing 2 SomeThing's. +struct SomeThingComparator : std::binary_function< volatile SomeThing *, volatile SomeThing *, bool > +{ + /// Returns true if left string < right string. + inline bool operator ()( volatile SomeThing * lhs, volatile SomeThing * rhs ) const + { + return ( lhs->GetLevel() < rhs->GetLevel() ); + } +}; + +// ---------------------------------------------------------------------------- + +void SortByLevel( SomeThingPool & pool ) +{ + const unsigned int count = pool.size(); + assert( 0 != count ); + SomeThingComparator comparator; + ::std::sort( pool.begin(), pool.end(), comparator ); +} + +// ---------------------------------------------------------------------------- + +void LockThese( SomeThingPool & pool ) +{ + + const unsigned int count = pool.size(); + assert( 0 != count ); + SortByLevel( pool ); + + volatile SomeThing * thing = pool[ count - 1 ]; + assert( NULL != thing ); + unsigned int level = thing->GetLevel(); + LevelMutexInfo::MutexContainer mutexes; + MutexErrors::Type result = MutexErrors::Success; + signed int ii = count - 1; + + for ( ; 0 <= ii; --ii ) + { + thing = pool[ ii ]; + assert( NULL != thing ); + if ( level != thing->GetLevel() ) + { + assert( mutexes.size() != 0 ); + result = LevelMutexInfo::MultiLock( mutexes ); + assert( result == MutexErrors::Success ); + mutexes.clear(); + level = thing->GetLevel(); + } + mutexes.push_back( &thing->GetMutex() ); + } + + assert( mutexes.size() != 0 ); + result = LevelMutexInfo::MultiLock( mutexes ); + assert( result == MutexErrors::Success ); + + for ( ii = count - 1; 0 <= ii; --ii ) + { + thing = pool[ ii ]; + assert( NULL != thing ); + assert( thing->GetMutex().IsLockedByCurrentThread() ); + } +} + +// ---------------------------------------------------------------------------- + +void UnlockThese( SomeThingPool & pool ) +{ + + const unsigned int count = pool.size(); + assert( 0 != count ); + SortByLevel( pool ); + + volatile SomeThing * thing = pool[ 0 ]; + assert( NULL != thing ); + unsigned int level = thing->GetLevel(); + LevelMutexInfo::MutexContainer mutexes; + MutexErrors::Type result = MutexErrors::Success; + unsigned int ii = 0; + + for ( ii = 0; ii < count; ++ii ) + { + thing = pool[ ii ]; + assert( NULL != thing ); + if ( level != thing->GetLevel() ) + { + assert( mutexes.size() != 0 ); + result = LevelMutexInfo::MultiUnlock( mutexes ); + assert( result == MutexErrors::Success ); + mutexes.clear(); + level = thing->GetLevel(); + } + mutexes.push_back( &thing->GetMutex() ); + } + + assert( mutexes.size() != 0 ); + result = LevelMutexInfo::MultiUnlock( mutexes ); + assert( result == MutexErrors::Success ); + + for ( ii = 0; ii < count; ++ii ) + { + thing = pool[ ii ]; + assert( NULL != thing ); + assert( !thing->GetMutex().IsLockedByCurrentThread() ); + } +} + +// ---------------------------------------------------------------------------- + +unsigned int CountLockedByThisThread( const SomeThingPool & pool ) +{ + const unsigned int count = pool.size(); + unsigned int locked = 0; + + for ( unsigned int ii = 0; ii < count; ++ii ) + { + const volatile SomeThing * thing = pool[ ii ]; + assert( NULL != thing ); + if ( thing->GetMutex().IsLockedByCurrentThread() ) + locked++; + } + + return locked; +} + +// ---------------------------------------------------------------------------- diff --git a/test/LevelMutex/Thing.hpp b/test/LevelMutex/Thing.hpp new file mode 100644 index 0000000..38ecfb9 --- /dev/null +++ b/test/LevelMutex/Thing.hpp @@ -0,0 +1,364 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Part of LevelMutex test program 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$ + + +// ---------------------------------------------------------------------------- + +#ifndef LOKI_TEST_LEVEL_MUTEX_THING_H +#define LOKI_TEST_LEVEL_MUTEX_THING_H + +#include +#include + +#include + + +// ---------------------------------------------------------------------------- + +void GoToSleep( unsigned int milliSeconds ); + +// ---------------------------------------------------------------------------- + +class ExceptionTossingMutex : public ::Loki::SleepLevelMutex +{ +public: + + enum ThrowingPolicy + { + Never, + Always, + Randomly + }; + + ExceptionTossingMutex( unsigned int level ); + + virtual ~ExceptionTossingMutex( void ); + + virtual ::Loki::MutexErrors::Type Lock( void ) volatile; + + virtual ::Loki::MutexErrors::Type TryLock( void ) volatile; + + virtual ::Loki::MutexErrors::Type Unlock( void ) volatile; + + inline ThrowingPolicy GetTossPolicy( void ) const { return m_policy; } + + inline void SetTossPolicy( ThrowingPolicy policy ) { m_policy = policy; } + +private: + + ExceptionTossingMutex( void ); + ExceptionTossingMutex( const ExceptionTossingMutex & ); + ExceptionTossingMutex & operator = ( const ExceptionTossingMutex & ); + + ThrowingPolicy m_policy; +}; + +typedef ::Loki::LevelMutex< ExceptionTossingMutex, 1, + ::Loki::JustReturnMutexError, ::Loki::NoMutexWait > ExceptionMutex; + +typedef ::Loki::LevelMutex< ::Loki::SleepLevelMutex, 1, + ::Loki::JustReturnMutexError, ::Loki::MutexSleepWaits > SleepMutex; + + +// ---------------------------------------------------------------------------- + +class TestResults +{ +public: + + static bool Create( unsigned int threadCount ); + + static void Destroy( void ); + + inline static TestResults * GetIt( void ) { return s_instance; } + + void Reset( unsigned int threadCount ); + + void SetResult( unsigned int threadIndex, unsigned int total, + unsigned int fails ); + + void OutputResults( void ); + +private: + + struct TestResult + { + TestResult( void ); + TestResult( const TestResult & that ); + ~TestResult( void ); + unsigned int m_total; + unsigned int m_fails; + }; + + typedef ::std::vector< TestResult, ::Loki::LokiAllocator< TestResult > > Results; + + explicit TestResults( unsigned int threadCount ); + + ~TestResults( void ); + + TestResults( void ); + TestResults( const TestResults & ); + TestResults & operator = ( const TestResults & ); + + static TestResults * s_instance; + + Results m_results; +}; + +// ---------------------------------------------------------------------------- + +class Thing +{ +public: + + typedef ::std::vector< volatile Thing * > ThingPool; + + static volatile Thing & GetIt( void ) { return *s_thing; } + + static void Init( unsigned int value ); + + static void Destroy( void ); + + static volatile Thing * GetFromPool( unsigned int index ); + + static bool MakePool( unsigned int count ); + + static unsigned int GetPoolSize( void ); + + static void DestroyPool( void ); + + void Print( unsigned int value, unsigned int index, unsigned int startSize ) const volatile; + + void Print( unsigned int value, unsigned int index, unsigned int startSize ) const; + + unsigned int GetValue( void ) const volatile { return m_value; } + + unsigned int GetValue( void ) const { return m_value; } + + void SetValue( unsigned int value ) volatile; + + void SetValue( unsigned int value ) { m_value = value; } + + inline volatile SleepMutex & GetMutex( void ) volatile { return m_mutex; } + + inline const volatile SleepMutex & GetMutex( void ) const volatile { return m_mutex; } + +private: + + explicit Thing( unsigned int value ); + + ~Thing( void ); + + Thing( void ); + Thing( const Thing & ); + Thing & operator = ( const Thing & ); + + static volatile Thing * s_thing; + + static ThingPool s_pool; + + static TestResults s_results; + + mutable volatile SleepMutex m_mutex; + unsigned int m_value; +}; + +typedef ::std::vector< Thing * > UnsafeThingPool; + +// ---------------------------------------------------------------------------- + +template < class Thingy > +class Unlocker +{ +public: + explicit inline Unlocker( volatile Thingy * thing ) : m_thing( thing ) {} + inline Unlocker( const Unlocker & that ) : m_thing( that.m_thing ) + { + const_cast< Unlocker & >( that ).m_thing = NULL; + } + inline ~Unlocker( void ) { Unlock(); } + inline void Unlock( void ) + { + if ( NULL != m_thing ) + m_thing->UnlockHierarchy(); + } +private: + volatile Thingy * m_thing; +}; + +// ---------------------------------------------------------------------------- + +class LevelThing +{ +public: + + typedef Unlocker< LevelThing > Unlocker; + + static volatile LevelThing * GetFromPool( unsigned int index ); + + static bool MakePool( unsigned int count ); + + static void DestroyPool( void ); + + Unlocker LockHierarchy( void ) volatile; + + void UnlockHierarchy( void ) volatile; + + void SetValue( unsigned int value ) volatile; + + void SetValue( unsigned int value ); + + inline unsigned int GetValue( void ) const volatile { return m_value; } + + inline unsigned int GetValue( void ) const { return m_value; } + + bool DoValuesMatch( unsigned int value ) const volatile; + + bool DoValuesMatch( unsigned int value ) const; + + inline volatile ::Loki::LevelMutexInfo & GetMutex( void ) volatile { return m_mutex; } + + inline const volatile ::Loki::LevelMutexInfo & GetMutex( void ) const volatile { return m_mutex; } + +private: + + typedef ::std::vector< volatile LevelThing * > LevelThingPool; + + LevelThing( unsigned int level, unsigned int place ); + + ~LevelThing( void ); + + LevelThing( void ); + LevelThing( const LevelThing & ); + LevelThing & operator = ( const LevelThing & ); + + static LevelThingPool s_pool; + + mutable volatile SleepMutex m_mutex; + const unsigned int m_place; + unsigned int m_value; +}; + +// ---------------------------------------------------------------------------- + +class SomeThing +{ +public: + + SomeThing( unsigned int level, unsigned int place ); + + ~SomeThing( void ); + + inline unsigned int GetLevel( void ) const volatile { return m_level; } + + void SetValue( unsigned int value ) volatile; + + void SetValue( unsigned int value ); + + inline unsigned int GetValue( void ) const volatile { return m_value; } + + inline unsigned int GetValue( void ) const { return m_value; } + + inline volatile ::Loki::LevelMutexInfo & GetMutex( void ) volatile { return m_mutex; } + + inline const volatile ::Loki::LevelMutexInfo & GetMutex( void ) const volatile { return m_mutex; } + +private: + + SomeThing( void ); + SomeThing( const SomeThing & ); + SomeThing & operator = ( const SomeThing & ); + + mutable volatile SleepMutex m_mutex; + const unsigned int m_place; + const unsigned int m_level; + unsigned int m_value; +}; + +typedef ::std::vector< volatile SomeThing * > SomeThingPool; + +typedef SomeThingPool::iterator SomeThingPoolIter; + +// ---------------------------------------------------------------------------- + +class ManyThingsPool +{ +public: + + explicit ManyThingsPool( unsigned int level, unsigned int count ); + + ~ManyThingsPool( void ); + + volatile SomeThing * GetFromPool( unsigned int index ); + + unsigned int GetCount( void ) const; + +private: + + ManyThingsPool( void ); + ManyThingsPool( const ManyThingsPool & ); + ManyThingsPool & operator = ( const ManyThingsPool & ); + + SomeThingPool m_pool; +}; + +// ---------------------------------------------------------------------------- + +class MultiLevelPool +{ +public: + + static void MakePool( unsigned int count, unsigned int thingCount ); + + static void DestroyPool( void ); + + static ManyThingsPool * GetFromPool( unsigned int index ); + + static unsigned int GetCount( void ); + +private: + + typedef ::std::vector< ManyThingsPool * > MultiThingPool; + + ~MultiLevelPool( void ); + MultiLevelPool( void ); + MultiLevelPool( const MultiLevelPool & ); + MultiLevelPool & operator = ( const MultiLevelPool & ); + + static MultiThingPool s_pool; +}; + +// ---------------------------------------------------------------------------- + +void CheckForMatchingValues( unsigned int & failCount, unsigned int & testCount, + unsigned int value, const SomeThingPool & pool ); + +void CheckForMatchingValues( unsigned int & failCount, unsigned int & testCount, + unsigned int value, const SomeThingPool & pool, bool locked ); + +void MakePool( SomeThingPool & pool ); + +void LockThese( SomeThingPool & pool ); + +void UnlockThese( SomeThingPool & pool ); + +unsigned int CountLockedByThisThread( const SomeThingPool & pool ); + +// ---------------------------------------------------------------------------- + +#endif diff --git a/test/LevelMutex/ThreadPool.hpp b/test/LevelMutex/ThreadPool.hpp new file mode 100644 index 0000000..655b552 --- /dev/null +++ b/test/LevelMutex/ThreadPool.hpp @@ -0,0 +1,226 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Part of LevelMutex test program 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. +// +//////////////////////////////////////////////////////////////////////////////// + + +// ---------------------------------------------------------------------------- + +#include + +// ---------------------------------------------------------------------------- + +typedef void * ( * CallFunction )( void * ); + +#if defined( _MSC_VER ) + #include + + #define LokiThread HANDLE + + #define LokiThreadCreate( handle, attr, func, arg ) \ + (int)(( * handle = ( HANDLE ) _beginthreadex ( nullptr, 0, func, arg, 0, nullptr ) ) == nullptr ) + + #define LokiThreadJoin( thread ) \ + ((::WaitForSingleObject((thread),INFINITE)!=WAIT_OBJECT_0) || !CloseHandle(thread)) + +#else + #include + + typedef void * ( * CallFunction )( void * ); + + #define LokiThread pthread_t + #define LokiThreadCreate( handle, attr, func, arg ) \ + pthread_create( handle, attr, func, arg ) + #define LokiThreadJoin( thread ) pthread_join( thread, NULL ) + +#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 + + +class ThreadPool; + +class Thread +{ +public: + + enum Status + { + Dead = 0, + Idle, + Starting, + Active + }; + + inline static volatile Thread * GetCurrentThread( void ) + { + return s_thread; + } + + bool WaitForThread( void ) volatile; + + inline CallFunction GetFunction( void ) const volatile { return m_func; } + + inline void * GetParameter( void ) const volatile { return m_parm; } + + inline Status GetStatus( void ) const volatile { return m_status; } + + inline const volatile ThreadPool * GetThreadPool( void ) const volatile + { + return m_owner; + } + + inline volatile ThreadPool * GetThreadPool( void ) volatile + { + return m_owner; + } + +private: + + friend class ThreadPool; + + inline static void SetCurrentThread( volatile Thread * thread ) + { + s_thread = thread; + } + + explicit Thread( volatile ThreadPool * owner ); + + Thread( volatile ThreadPool * owner, CallFunction function, void * parm ); + + ~Thread( void ); + + /// Default constructor not implemented. + Thread( void ); + + /// not implemented. + Thread( const Thread & ); + + /// not implemented. + Thread & operator = ( const Thread & ); + + bool IsValid( const volatile ThreadPool * owner ) const volatile; + + static LOKI_THREAD_LOCAL volatile Thread * s_thread; + + LokiThread m_thread; + CallFunction m_func; + void * m_parm; + Status m_status; + bool m_stop; + volatile ThreadPool * const m_owner; +}; + +class ThreadPool +{ +public: + + explicit ThreadPool( unsigned int threadCount = 0 ); + + ~ThreadPool( void ); + + unsigned int Create( unsigned int threadCount ) volatile; + + volatile Thread * Start( CallFunction function, void * parm ) volatile; + + void Join( unsigned int index ) const volatile; + + void JoinAll( void ) const volatile; + + unsigned int GetCount( void ) const volatile; + + unsigned int GetCount( Thread::Status status ) const volatile; + + volatile const Thread * GetThread( unsigned int index ) const volatile; + + inline volatile Thread * GetThread( unsigned int index ) volatile + { + return const_cast< volatile Thread * >( + const_cast< const volatile ThreadPool * >( this )-> + GetThread( index ) ); + } + +private: + + /** @class Checker + Performs validity check on ThreadPool 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 a release build. A + checker is created inside many of member functions so that it's destructor + gets called when the function exits. + */ + class Checker + { + public: + inline explicit Checker( const volatile ThreadPool * mutex ) : + m_pool( mutex ) {} + inline ~Checker( void ) { m_pool->IsValid(); } + private: + Checker( void ); + Checker( const Checker & ); + Checker & operator = ( const Checker & ); + const volatile ThreadPool * m_pool; + }; + + /// Copy-constructor is not implemented. + ThreadPool( const ThreadPool & ); + + /// Copy-assignment operator is not implemented. + ThreadPool & operator = ( const ThreadPool & ); + +#if defined( _MSC_VER ) + static unsigned int __stdcall TopFunction( void * p ); +#else + static void * TopFunction( void * p ); +#endif + + /** Returns true if no class invariant broken, otherwise asserts. This function + only gets called in debug builds. + */ + bool IsValid( void ) const volatile; + + typedef ::std::vector< volatile Thread * > Threads; + typedef Threads::iterator ThreadsIter; + typedef Threads::const_iterator ThreadsCIter; + + Threads m_threads; +}; + +// ---------------------------------------------------------------------------- diff --git a/test/LevelMutex/main.cpp b/test/LevelMutex/main.cpp new file mode 100644 index 0000000..08450d5 --- /dev/null +++ b/test/LevelMutex/main.cpp @@ -0,0 +1,681 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Part of LevelMutex test program 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$ + + +// ---------------------------------------------------------------------------- + + +#include "LevelMutex.h" + +#include "MultiThreadTests.hpp" +#include "Thing.hpp" + +#include +#include +#include + + +// define nullptr even though new compilers will have this keyword just so we +// have a consistent and easy way of identifying which uses of 0 mean null. +#define nullptr 0 + + +using namespace ::std; +using namespace ::Loki; + + +typedef ::Loki::LevelMutex< ::Loki::SpinLevelMutex, 1, + ::Loki::JustReturnMutexError, ::Loki::NoMutexWait > SpinMutex; + + +// ---------------------------------------------------------------------------- + +void SingleThreadSimpleTest( void ) +{ + + const unsigned int priorLevel = GetCurrentThreadsLevel(); + const unsigned int priorLockCount = CountLocksInCurrentThread(); + const unsigned int priorMutexCount = CountMutexesInCurrentThread(); + const unsigned int priorLevelMutexCount = CountMutexesAtCurrentLevel(); + if ( LevelMutexInfo::UnlockedLevel == priorLevel ) + { + assert( priorLockCount == 0 ); + assert( priorMutexCount == 0 ); + assert( priorLevelMutexCount == 0 ); + } + else + { + assert( priorLockCount != 0 ); + assert( priorMutexCount != 0 ); + assert( priorLevelMutexCount != 0 ); + assert( priorLevelMutexCount <= priorMutexCount ); + assert( priorMutexCount <= priorLockCount ); + } + + SpinMutex mutex1( 1 ); + SpinMutex mutex2( 2 ); + SpinMutex mutex1a( 1 ); + + bool okay = ( mutex1.GetLevel() == 1 ); + assert( okay ); + okay = ( mutex2.GetLevel() == 2 ); + assert( okay ); + okay = ( !mutex1.IsLocked() ); + assert( okay ); + okay = ( !mutex2.IsLocked() ); + assert( okay ); + okay = ( mutex1.GetLockCount() == 0 ); + assert( okay ); + okay = ( mutex2.GetLockCount() == 0 ); + assert( okay ); + MutexErrors::Type result = mutex1.Unlock(); + assert( MutexErrors::WasntLocked == result ); + result = mutex2.Unlock(); + assert( MutexErrors::WasntLocked == result ); + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + + // Check for attempt to lock mutex with level higher than current level. + result = mutex1.Lock(); + assert( result == MutexErrors::Success ); + okay = ( GetCurrentThreadsLevel() == mutex1.GetLevel() ); + assert( okay ); + try + { + result = mutex2.Lock(); + } + catch ( const MutexException & ex1 ) + { + assert( MutexErrors::LevelTooHigh == ex1.GetReason() ); + } + catch ( ... ) + { + assert( false ); + } + result = mutex2.Unlock(); + assert( result == MutexErrors::WasntLocked ); + okay = ( GetCurrentThreadsLevel() == mutex1.GetLevel() ); + assert( okay ); + try + { + // Now check for attempt to lock mutex at same level as current one. + result = mutex1a.Lock(); + } + catch ( const MutexException & ex2 ) + { + assert( MutexErrors::LevelTooHigh == ex2.GetReason() ); + } + catch ( ... ) + { + assert( false ); + } + result = mutex1.Unlock(); + assert( result == MutexErrors::Success ); + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + + // Check for unlocking mutex that is not current lock. + result = mutex2.Lock(); + assert( result == MutexErrors::Success ); + okay = ( GetCurrentThreadsLevel() == mutex2.GetLevel() ); + assert( okay ); + result = mutex1.Lock(); + assert( result == MutexErrors::Success ); + try + { + result = mutex2.Unlock(); + } + catch ( const MutexException & ex1 ) + { + assert( MutexErrors::LevelTooHigh == ex1.GetReason() ); + } + catch ( ... ) + { + assert( false ); + } + okay = ( GetCurrentThreadsLevel() == mutex1.GetLevel() ); + assert( okay ); + result = mutex1.Unlock(); + assert( result == MutexErrors::Success ); + okay = ( GetCurrentThreadsLevel() == mutex2.GetLevel() ); + assert( okay ); + result = mutex1.Unlock(); + assert( result == MutexErrors::WasntLocked ); + result = mutex2.Unlock(); + assert( result == MutexErrors::Success ); + + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + okay = ( CountLocksInCurrentThread() == priorLockCount ); + assert( okay ); + okay = ( CountMutexesInCurrentThread() == priorMutexCount ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); +} + +// ---------------------------------------------------------------------------- + +void SingleThreadReentrantTest( void ) +{ + + const unsigned int priorLevel = GetCurrentThreadsLevel(); + const unsigned int priorLockCount = CountLocksInCurrentThread(); + const unsigned int priorMutexCount = CountMutexesInCurrentThread(); + const unsigned int priorLevelMutexCount = CountMutexesAtCurrentLevel(); + if ( LevelMutexInfo::UnlockedLevel == priorLevel ) + { + assert( priorLockCount == 0 ); + assert( priorMutexCount == 0 ); + assert( priorLevelMutexCount == 0 ); + } + else + { + assert( priorLockCount != 0 ); + assert( priorMutexCount != 0 ); + assert( priorLevelMutexCount != 0 ); + assert( priorLevelMutexCount <= priorMutexCount ); + assert( priorMutexCount <= priorLockCount ); + } + + SpinMutex mutex1( 1 ); + SpinMutex mutex2( 2 ); + bool okay = ( mutex2.GetLevel() != mutex1.GetLevel() ); + assert( okay ); + + okay = ( mutex2.GetLockCount() == 0 ); + assert( okay ); + okay = ( mutex1.GetLockCount() == 0 ); + assert( okay ); + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + okay = ( CountLocksInCurrentThread() == priorLockCount ); + assert( okay ); + okay = ( CountMutexesInCurrentThread() == priorMutexCount ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + + MutexErrors::Type result = mutex2.Lock(); + assert( result == MutexErrors::Success ); + okay = ( GetCurrentThreadsLevel() == mutex2.GetLevel() ); + assert( okay ); + okay = ( mutex2.GetLockCount() == 1 ); + assert( okay ); + result = mutex2.Lock(); + assert( MutexErrors::Success == result ); + okay = ( mutex2.GetLockCount() == 2 ); + assert( okay ); + result = mutex2.TryLock(); + assert( result == MutexErrors::Success ); + okay = ( mutex2.GetLockCount() == 3 ); + assert( okay ); + result = mutex2.Unlock(); + okay = ( mutex2.GetLockCount() == 2 ); + assert( okay ); + result = mutex1.Lock(); + assert( MutexErrors::Success == result ); + okay = ( GetCurrentThreadsLevel() == mutex1.GetLevel() ); + assert( okay ); + okay = ( mutex1.GetLockCount() == 1 ); + assert( okay ); + result = mutex1.Lock(); + assert( MutexErrors::Success == result ); + okay = ( CountLocksInCurrentThread() == priorLockCount + 4 ); + assert( okay ); + okay = ( CountMutexesInCurrentThread() == priorMutexCount + 2 ); + assert( okay ); + okay = ( mutex1.GetLockCount() == 2 ); + assert( okay ); + result = mutex1.Unlock(); + assert( MutexErrors::Success == result ); + okay = ( mutex1.GetLockCount() == 1 ); + assert( okay ); + result = mutex1.Unlock(); + assert( MutexErrors::Success == result ); + okay = ( mutex1.GetLockCount() == 0 ); + assert( okay ); + okay = ( mutex2.GetLockCount() == 2 ); + assert( okay ); + result = mutex2.Unlock(); + assert( MutexErrors::Success == result ); + okay = ( mutex2.GetLockCount() == 1 ); + assert( okay ); + result = mutex2.Unlock(); + assert( MutexErrors::Success == result ); + + // Now check if current thread has no locks. + okay = ( CountLocksInCurrentThread() == priorLockCount ); + assert( okay ); + okay = ( CountMutexesInCurrentThread() == priorMutexCount ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + okay = ( mutex2.GetLockCount() == 0 ); + assert( okay ); + okay = ( mutex1.GetLockCount() == 0 ); + assert( okay ); + + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + okay = ( CountLocksInCurrentThread() == priorLockCount ); + assert( okay ); + okay = ( CountMutexesInCurrentThread() == priorMutexCount ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); +} + +// ---------------------------------------------------------------------------- + +void SingleThreadSimpleMultiLockTest( void ) +{ + + const unsigned int priorLevel = GetCurrentThreadsLevel(); + const unsigned int priorLockCount = CountLocksInCurrentThread(); + const unsigned int priorMutexCount = CountMutexesInCurrentThread(); + const unsigned int priorLevelMutexCount = CountMutexesAtCurrentLevel(); + if ( LevelMutexInfo::UnlockedLevel == priorLevel ) + { + assert( priorLockCount == 0 ); + assert( priorMutexCount == 0 ); + assert( priorLevelMutexCount == 0 ); + } + else + { + assert( priorLockCount != 0 ); + assert( priorMutexCount != 0 ); + assert( priorLevelMutexCount != 0 ); + assert( priorLevelMutexCount <= priorMutexCount ); + assert( priorMutexCount <= priorLockCount ); + } + + SpinMutex mutex1( 1 ); + SpinMutex mutex2( 1 ); + bool okay = ( mutex2.GetLevel() == mutex1.GetLevel() ); + assert( okay ); + + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + LevelMutexInfo::MutexContainer mutexes; + mutexes.push_back( &mutex1 ); + mutexes.push_back( &mutex2 ); + + MutexErrors::Type result = LevelMutexInfo::MultiLock( mutexes ); // locks: 1, 2 + assert( MutexErrors::Success == result ); + okay = ( GetCurrentThreadsLevel() == mutex1.GetLevel() ); + assert( okay ); + result = LevelMutexInfo::MultiUnlock( mutexes ); // locks: none + assert( MutexErrors::Success == result ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + + mutexes.clear(); + mutexes.push_back( &mutex1 ); + mutexes.push_back( &mutex2 ); + mutexes.push_back( &mutex1 ); + result = LevelMutexInfo::MultiLock( mutexes ); // locks: none + assert( MutexErrors::DuplicateMutex == result ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + + mutexes.clear(); + mutexes.push_back( &mutex1 ); + mutexes.push_back( nullptr ); + mutexes.push_back( &mutex2 ); + result = LevelMutexInfo::MultiLock( mutexes ); // locks: none + assert( MutexErrors::NullMutexPointer == result ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + + SpinMutex mutex3( 3 ); + mutexes.clear(); + mutexes.push_back( &mutex1 ); + mutexes.push_back( &mutex2 ); + mutexes.push_back( &mutex3 ); + result = LevelMutexInfo::MultiLock( mutexes ); // locks: none + assert( MutexErrors::WrongLevel == result ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + + result = mutex1.Lock(); // locks: 1 + assert( MutexErrors::Success == result ); + mutexes.clear(); + mutexes.push_back( &mutex1 ); + mutexes.push_back( &mutex2 ); + try + { + result = LevelMutexInfo::MultiLock( mutexes ); // locks: 1 + } + catch ( const MutexException & ex1 ) + { + assert( MutexErrors::LevelTooHigh == ex1.GetReason() ); + } + catch ( ... ) + { + assert( false ); + } + result = mutex1.Unlock(); // locks: none + assert( MutexErrors::Success == result ); + + result = LevelMutexInfo::MultiLock( mutexes ); // locks: 1, 2 + assert( MutexErrors::Success == result ); + const bool m1IsFirst = ( &mutex1 > &mutex2 ); + LevelMutexInfo * first = ( m1IsFirst ) ? &mutex1 : &mutex2; + LevelMutexInfo * second = ( m1IsFirst ) ? &mutex2 : &mutex1; + result = first->Unlock(); // locks: 2 + assert( MutexErrors::Success == result ); + result = LevelMutexInfo::MultiUnlock( mutexes ); // locks: 2 + assert( MutexErrors::NotRecentLock == result ); + result = second->Unlock(); // locks: none + assert( MutexErrors::Success == result ); + + // Now combine some calls to TryLock with a call to MultiLock. + mutexes.clear(); + mutexes.push_back( &mutex1 ); + mutexes.push_back( &mutex2 ); + result = LevelMutexInfo::MultiLock( mutexes ); // locks: 1, 2 + assert( MutexErrors::Success == result ); + result = mutex2.TryLock(); // locks: 1, 2+ + assert( MutexErrors::Success == result ); + okay = ( mutex2.GetLockCount() == 2 ); + assert( okay ); + result = mutex2.Unlock(); // locks: 1, 2 + assert( MutexErrors::Success == result ); + result = mutex1.TryLock(); // locks: 1+, 2 + assert( result == MutexErrors::Success ); + okay = ( mutex1.GetLockCount() == 2 ); + assert( okay ); + result = mutex2.TryLock(); // locks: 1+, 2+ + assert( result == MutexErrors::Success ); + okay = ( mutex2.GetLockCount() == 2 ); + assert( okay ); + result = mutex2.Unlock(); // locks: 1+, 2 + assert( MutexErrors::Success == result ); + result = mutex1.Unlock(); // locks: 1, 2 + assert( MutexErrors::Success == result ); + result = LevelMutexInfo::MultiUnlock( mutexes ); // locks: 1, 2 + assert( MutexErrors::Success == result ); + + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + + SpinMutex mutex4( 1 ); + result = mutex4.Lock(); + assert( MutexErrors::Success == result ); + mutexes.clear(); + mutexes.push_back( &mutex1 ); + mutexes.push_back( &mutex2 ); + + try + { + result = LevelMutexInfo::MultiLock( mutexes ); + } + catch ( const MutexException & ex1 ) + { + assert( MutexErrors::LevelTooHigh == ex1.GetReason() ); + } + catch ( ... ) + { + assert( false ); + } + mutexes.clear(); + mutexes.push_back( &mutex1 ); + mutexes.push_back( &mutex4 ); + result = LevelMutexInfo::MultiUnlock( mutexes ); + assert( MutexErrors::NotRecentLock == result ); + + mutexes.clear(); + mutexes.push_back( &mutex1 ); + mutexes.push_back( &mutex2 ); + result = LevelMutexInfo::MultiUnlock( mutexes ); + assert( MutexErrors::NotRecentLock == result ); + result = mutex4.Unlock(); + assert( MutexErrors::Success == result ); + + // Make sure current thread has released locks acquired in this function. + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); + + // What if this tries to unlock multiple mutexes when nothing is locked? + mutexes.clear(); + mutexes.push_back( &mutex1 ); + mutexes.push_back( &mutex2 ); + result = LevelMutexInfo::MultiUnlock( mutexes ); + assert( MutexErrors::NotRecentLock == result ); + + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + okay = ( CountLocksInCurrentThread() == priorLockCount ); + assert( okay ); + okay = ( CountMutexesInCurrentThread() == priorMutexCount ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); +} + +// ---------------------------------------------------------------------------- + +void SingleThreadExceptionTest( void ) +{ + + const unsigned int priorLevel = GetCurrentThreadsLevel(); + const unsigned int priorLockCount = CountLocksInCurrentThread(); + const unsigned int priorMutexCount = CountMutexesInCurrentThread(); + const unsigned int priorLevelMutexCount = CountMutexesAtCurrentLevel(); + if ( LevelMutexInfo::UnlockedLevel == priorLevel ) + { + assert( priorLockCount == 0 ); + assert( priorMutexCount == 0 ); + assert( priorLevelMutexCount == 0 ); + } + else + { + assert( priorLockCount != 0 ); + assert( priorMutexCount != 0 ); + assert( priorLevelMutexCount != 0 ); + assert( priorLevelMutexCount <= priorMutexCount ); + assert( priorMutexCount <= priorLockCount ); + } + + ExceptionMutex mutex1a( 1 ); + mutex1a.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Always ); + ExceptionMutex mutex2a( 2 ); + mutex2a.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Never ); + MutexErrors::Type result = MutexErrors::Success; + + try + { + mutex1a.Lock(); + assert( false ); + } + catch ( ... ) + { + assert( true ); + } + + try + { + result = mutex2a.Lock(); + assert( MutexErrors::Success == result ); + mutex1a.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Never ); + result = mutex1a.Lock(); + assert( MutexErrors::Success == result ); + result = mutex1a.Unlock(); + assert( MutexErrors::Success == result ); + result = mutex2a.Unlock(); + assert( MutexErrors::Success == result ); + } + catch ( ... ) + { + assert( false ); + } + + ExceptionMutex mutex2b( 2 ); + mutex2b.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Always ); + ExceptionMutex mutex2c( 2 ); + mutex2c.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Never ); + ExceptionMutex mutex2d( 2 ); + mutex2d.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Never ); + LevelMutexInfo::MutexContainer mutexes; + try + { + mutexes.push_back( &mutex2a ); + mutexes.push_back( &mutex2b ); + mutexes.push_back( &mutex2c ); + mutexes.push_back( &mutex2d ); + result = LevelMutexInfo::MultiLock( mutexes ); + assert( false ); + } + catch ( ... ) + { + assert( true ); + } + + bool okay = ( !mutex2a.IsLockedByCurrentThread() ); + assert( okay ); + okay = ( !mutex2b.IsLockedByCurrentThread() ); + assert( okay ); + okay = ( !mutex2c.IsLockedByCurrentThread() ); + assert( okay ); + okay = ( !mutex2d.IsLockedByCurrentThread() ); + assert( okay ); + + mutex2b.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Never ); + + ExceptionMutex mutex3a( 3 ); + mutex3a.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Never ); + ExceptionMutex mutex3b( 3 ); + mutex3b.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Never ); + mutexes.clear(); + try + { + mutexes.push_back( &mutex3a ); + mutexes.push_back( &mutex3b ); + result = LevelMutexInfo::MultiLock( mutexes ); // 3a, 3b + assert( true ); + assert( MutexErrors::Success == result ); + okay = ( mutex3a.IsLockedByCurrentThread() ); + assert( okay ); + okay = ( mutex3b.IsLockedByCurrentThread() ); + assert( okay ); + } + catch ( ... ) + { + assert( false ); + } + + try + { + mutex2c.GetMutexPolicy().SetTossPolicy( ExceptionTossingMutex::Always ); + mutexes.clear(); + mutexes.push_back( &mutex2a ); + mutexes.push_back( &mutex2b ); + mutexes.push_back( &mutex2c ); + mutexes.push_back( &mutex2d ); + result = LevelMutexInfo::MultiLock( mutexes ); + assert( false ); + } + catch ( ... ) + { + assert( true ); + okay = ( !mutex2a.IsLockedByCurrentThread() ); + assert( okay ); + okay = ( !mutex2b.IsLockedByCurrentThread() ); + assert( okay ); + okay = ( !mutex2c.IsLockedByCurrentThread() ); + assert( okay ); + okay = ( !mutex2d.IsLockedByCurrentThread() ); + assert( okay ); + okay = ( mutex3a.IsLockedByCurrentThread() ); + assert( okay ); + okay = ( mutex3b.IsLockedByCurrentThread() ); + assert( okay ); + } + + mutexes.clear(); + mutexes.push_back( &mutex3a ); + mutexes.push_back( &mutex3b ); + result = LevelMutexInfo::MultiUnlock( mutexes ); + assert( MutexErrors::Success == result ); + + okay = ( GetCurrentThreadsLevel() == priorLevel ); + assert( okay ); + okay = ( CountLocksInCurrentThread() == priorLockCount ); + assert( okay ); + okay = ( CountMutexesInCurrentThread() == priorMutexCount ); + assert( okay ); + okay = ( CountMutexesAtCurrentLevel() == priorLevelMutexCount ); + assert( okay ); +} + +// ---------------------------------------------------------------------------- + +int main( unsigned int argc, const char * const argv[] ) +{ + + cout << "Starting LevelMutex tests." << endl; + if ( 1 < argc ) + { + if ( ::strcmp( argv[1], "-r" ) == 0 ) + SetToRedoSingleTests( true ); + } + + try + { + SingleThreadSimpleTest(); + SingleThreadReentrantTest(); + SingleThreadSimpleMultiLockTest(); + SingleThreadComplexMultiLockTest( true ); + SingleThreadExceptionTest(); + + MultiThreadSimpleTest(); + MultiThreadTryLockTest(); + MultiThreadReentrantTest(); + MultiThreadMultiLockTest(); + MultiThreadRandomMultiLockTest(); + + // This tests for single locking in a multi-threaded environment. + MultiThreadHierarchySingleLockTest(); + // This tests for multi-locking and single locking in a multi-threaded environment. + MultiThreadHierarchyMultiLockTest(); + } + catch ( ... ) + { + cout << "Caught unknown exception!" << endl; + } + + ::system( "pause" ); + return 0; +} + +// ----------------------------------------------------------------------------