| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- ///////////////////////////////////////////////////////////////////////////////
- // Copyright (c) Electronic Arts Inc. All rights reserved.
- ///////////////////////////////////////////////////////////////////////////////
- #include "TestThread.h"
- #include <EATest/EATest.h>
- #include <EAStdC/EAStopwatch.h>
- #include <EAStdC/EAString.h>
- #include <eathread/eathread_futex.h>
- #include <eathread/eathread_thread.h>
- #include <eathread/eathread_atomic.h>
- #include <eathread/eathread_mutex.h>
- #include <stdlib.h>
- #include <string.h>
- #if defined(EA_PLATFORM_MICROSOFT)
- #pragma warning(push, 0)
- #include <Windows.h>
- #pragma warning(pop)
- #endif
- #if defined(_MSC_VER)
- #pragma warning(disable: 4996) // This function or variable may be unsafe / deprecated.
- #endif
- using namespace EA::Thread;
- typedef intptr_t (*FutexTestThreadFunction)(void*);
- const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
- ///////////////////////////////////////////////////////////////////////////////
- // FWorkData
- //
- struct FWorkData
- {
- volatile bool mbShouldQuit;
- Futex mFutex;
- char mUnused[7000]; // We intentionally put a big buffer between these.
- int mThreadWithLock[kMaxConcurrentThreadCount];
- AtomicInt32 mThreadCount;
- AtomicInt32 mnErrorCount;
- FWorkData() : mbShouldQuit(false), mFutex(), mThreadCount(0), mnErrorCount(0) { memset(mThreadWithLock, 0, sizeof(mThreadWithLock)); }
- private:
- // Prevent default generation of these functions by not defining them
- FWorkData(const FWorkData& rhs); // copy constructor
- FWorkData& operator=(const FWorkData& rhs); // assignment operator
- };
- ///////////////////////////////////////////////////////////////////////////////
- // TestThreadFutexSingle
- //
- static int TestThreadFutexSingle()
- {
- // Formerly declared as a global because VC++ for XBox 360 was found to be possibly throwing
- // away the atomic operations when it found that the futex was local to the function.
-
- // Now that Xbox 360 support has been removed, the futex has been tentatively moved back to
- // local scope. Of course, if this problem manifests again, the change will be reversed.
- Futex futexSingle;
- int nErrorCount(0);
- EA::UnitTest::ReportVerbosity(1, "\nSimple test...\n");
- // Single-threaded tests
- int nLockCount;
- EATEST_VERIFY_MSG(!futexSingle.HasLock(), "Futex failure.");
- nLockCount = futexSingle.GetLockCount();
- EATEST_VERIFY_MSG(nLockCount == 0, "Futex failure.");
- futexSingle.Lock();
- nLockCount = futexSingle.GetLockCount();
- EATEST_VERIFY_MSG(nLockCount == 1, "Futex failure.");
- EATEST_VERIFY_MSG(futexSingle.HasLock(), "Futex failure.");
- futexSingle.Lock();
- nLockCount = futexSingle.GetLockCount();
- EATEST_VERIFY_MSG(nLockCount == 2, "Futex failure.");
- futexSingle.Unlock();
- nLockCount = futexSingle.GetLockCount();
- EATEST_VERIFY_MSG(nLockCount == 1, "Futex failure.");
- futexSingle.Unlock();
- nLockCount = futexSingle.GetLockCount();
- EATEST_VERIFY_MSG(nLockCount == 0, "Futex failure.");
- futexSingle.Lock();
- nLockCount = futexSingle.GetLockCount();
- EATEST_VERIFY_MSG(nLockCount == 1, "Futex failure.");
- futexSingle.Unlock();
- nLockCount = futexSingle.GetLockCount();
- EATEST_VERIFY_MSG(nLockCount == 0, "Futex failure.");
- EATEST_VERIFY_MSG(!futexSingle.HasLock(), "Futex failure.");
- return nErrorCount;
- }
- ///////////////////////////////////////////////////////////////////////////////
- // FutexTestThreadFunction1
- //
- static intptr_t FutexTestThreadFunction1(void* pvWorkData)
- {
- int nErrorCount = 0;
- FWorkData* pWorkData = (FWorkData*)pvWorkData;
- const int32_t nThreadIndex = pWorkData->mThreadCount++;
- ThreadId threadId = GetThreadId();
- EA::UnitTest::ReportVerbosity(1, "FutexTestThreadFunction1 created: %s, thread index %d\n", EAThreadThreadIdToString(threadId), nThreadIndex);
- while(!pWorkData->mbShouldQuit)
- {
- int nRecursiveLockCount(rand() % 3);
- int i;
- for(i = 0; i < nRecursiveLockCount; ++i)
- {
- pWorkData->mFutex.Lock();
- pWorkData->mThreadWithLock[nThreadIndex]++;
- for(int j = 0; j < kMaxConcurrentThreadCount; ++j)
- {
- // Make sure this thread has the lock.
- EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId));
- }
- ThreadCooperativeYield(); // Used by cooperative threading platforms.
- }
- for(; nRecursiveLockCount > 0; --nRecursiveLockCount)
- {
- pWorkData->mThreadWithLock[nThreadIndex]--;
- // Verify that N locks are set.
- int nLockCount = pWorkData->mFutex.GetLockCount();
- EATEST_VERIFY_MSG(nLockCount == nRecursiveLockCount, "Futex failure.");
- if(nLockCount != nRecursiveLockCount)
- pWorkData->mbShouldQuit = true;
-
- // Verify the unlock result.
- pWorkData->mFutex.Unlock();
- nLockCount = pWorkData->mFutex.GetLockCount();
- EATEST_VERIFY_MSG((nRecursiveLockCount == 1) || (nLockCount != nRecursiveLockCount), "Futex failure.");
- if(nRecursiveLockCount > 1) // If we have remaining locks...
- {
- for(int j = 0; j < kMaxConcurrentThreadCount; ++j)
- {
- // Make sure this thread has the lock.
- EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId));
- }
- }
-
- ThreadCooperativeYield(); // Used by cooperative threading platforms.
- }
- if(nErrorCount)
- pWorkData->mbShouldQuit = true;
- else
- EA::UnitTest::ThreadSleepRandom(100, 200);
- }
- pWorkData->mnErrorCount += nErrorCount;
- return 0;
- }
- ///////////////////////////////////////////////////////////////////////////////
- // FutexTestThreadFunction2
- //
- // In this function we test Lock, attempting to detect memory synchronization
- // problems that could occur with an incorrect Futex implementation.
- //
- static intptr_t FutexTestThreadFunction2(void* pvWorkData)
- {
- int nErrorCount = 0;
- FWorkData* pWorkData = (FWorkData*)pvWorkData;
- const int32_t nThreadIndex = pWorkData->mThreadCount++;
- ThreadId threadId = GetThreadId();
- EA::UnitTest::ReportVerbosity(1, "FutexTestThreadFunction2 created: %s, thread index %d\n", EAThreadThreadIdToString(threadId), nThreadIndex);
- while(!pWorkData->mbShouldQuit)
- {
- pWorkData->mFutex.Lock();
- pWorkData->mThreadWithLock[nThreadIndex] = 1;
- for(int j = 0; j < kMaxConcurrentThreadCount; ++j)
- {
- // Make sure this thread has the lock.
- EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId));
- }
- pWorkData->mThreadWithLock[nThreadIndex] = 0;
- pWorkData->mFutex.Unlock();
- ThreadCooperativeYield(); // Used by cooperative threading platforms.
- if(nErrorCount)
- pWorkData->mbShouldQuit = true;
- }
- pWorkData->mnErrorCount += nErrorCount;
- return 0;
- }
- ///////////////////////////////////////////////////////////////////////////////
- // FutexTestThreadFunction3
- //
- // In this function we test TryLock.
- //
- static intptr_t FutexTestThreadFunction3(void* pvWorkData)
- {
- int nErrorCount = 0;
- FWorkData* pWorkData = (FWorkData*)pvWorkData;
- const int32_t nThreadIndex = pWorkData->mThreadCount++;
- ThreadId threadId = GetThreadId();
- EA::UnitTest::ReportVerbosity(1, "FutexTestThreadFunction3 created: %s, thread index %d\n", EAThreadThreadIdToString(threadId), nThreadIndex);
- for(int i = 0; !pWorkData->mbShouldQuit; ++i)
- {
- // We make sure to mix TryLock usage with Lock usage.
- bool bResult;
- if(((i % 4) != 0)) // 3/4 of the time, use TryLock, 1/4 of the time, use Lock.
- bResult = pWorkData->mFutex.TryLock();
- else
- {
- pWorkData->mFutex.Lock();
- bResult = true;
- }
- if(bResult)
- {
- pWorkData->mThreadWithLock[nThreadIndex] = 1;
- for(int j = 0; j < kMaxConcurrentThreadCount; ++j)
- {
- // Make sure this thread has the lock.
- EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId));
- }
- pWorkData->mThreadWithLock[nThreadIndex] = 0;
- pWorkData->mFutex.Unlock();
- if(nErrorCount)
- pWorkData->mbShouldQuit = true;
- }
- ThreadCooperativeYield(); // Used by cooperative threading platforms.
- }
- pWorkData->mnErrorCount += nErrorCount;
- return 0;
- }
- ///////////////////////////////////////////////////////////////////////////////
- // TestThreadFutexMulti
- //
- static int TestThreadFutexMulti(FutexTestThreadFunction pFutexTestThreadFunction, int nThreadCount)
- {
- int nErrorCount(0);
- FWorkData* const pWorkData = new FWorkData;
- Thread thread[kMaxConcurrentThreadCount];
- Thread::Status status;
- int i;
- EA::UnitTest::ReportVerbosity(1, "Multithreaded test...\n");
- if(nThreadCount > kMaxConcurrentThreadCount)
- nThreadCount = kMaxConcurrentThreadCount;
- for(i = 0; i < nThreadCount; i++)
- thread[i].Begin(pFutexTestThreadFunction, pWorkData);
- EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
- pWorkData->mbShouldQuit = true;
- for(i = 0; i < nThreadCount; i++)
- {
- status = thread[i].WaitForEnd(GetThreadTime() + 30000);
- EATEST_VERIFY_MSG(status == EA::Thread::Thread::kStatusEnded, "Futex/Thread failure: Thread(s) didn't end.");
- }
- nErrorCount += (int)pWorkData->mnErrorCount;
- delete pWorkData;
- return nErrorCount;
- }
- ///////////////////////////////////////////////////////////////////////////////
- // TestThreadFutexSpeed
- //
- static int TestThreadFutexSpeed()
- {
- int nErrorCount = 0;
- size_t i;
- uint64_t t0, t1, tDelta;
- const size_t kLoopCount = 1000000;
- EA::UnitTest::ReportVerbosity(1, "\nSpeed test...\n");
- //////////////////////////////////////////////////
- // Futex
- {
- Futex f;
- t0 = EA::StdC::Stopwatch::GetCPUCycle();
- f.Lock();
- f.Unlock();
- t1 = EA::StdC::Stopwatch::GetCPUCycle();
- t0 = EA::StdC::Stopwatch::GetCPUCycle();
- for(i = 0; i < kLoopCount; i++)
- {
- f.Lock();
- f.Unlock();
- }
- t1 = EA::StdC::Stopwatch::GetCPUCycle();
- tDelta = t1 - t0;
- EA::UnitTest::ReportVerbosity(1, "Futex time (ticks): %" PRIu64 "\n", tDelta);
- }
- //////////////////////////////////////////////////
- //////////////////////////////////////////////////
- // Mutex
- {
- EA::Thread::Mutex m;
- t0 = EA::StdC::Stopwatch::GetCPUCycle();
- m.Lock();
- m.Unlock();
- t1 = EA::StdC::Stopwatch::GetCPUCycle();
- t0 = EA::StdC::Stopwatch::GetCPUCycle();
- for(i = 0; i < kLoopCount; i++)
- {
- m.Lock();
- m.Unlock();
- }
- t1 = EA::StdC::Stopwatch::GetCPUCycle();
- tDelta = t1 - t0;
- EA::UnitTest::ReportVerbosity(1, "Mutex time (ticks): %" PRIu64 "\n", tDelta);
- }
- //////////////////////////////////////////////////
- #if defined(EA_PLATFORM_MICROSOFT) && !defined(EA_PLATFORM_WINDOWS_PHONE) && !(defined(EA_PLATFORM_WINDOWS) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP))
- //////////////////////////////////////////////////
- // HMUTEX
- {
- HANDLE hMutex = CreateMutexA(NULL, false, NULL);
- if (hMutex != NULL)
- {
- WaitForSingleObject(hMutex, INFINITE);
- ReleaseMutex(hMutex);
- t0 = EA::StdC::Stopwatch::GetCPUCycle();
- for(i = 0; i < kLoopCount; i++)
- {
- WaitForSingleObject(hMutex, INFINITE);
- ReleaseMutex(hMutex);
- }
- t1 = EA::StdC::Stopwatch::GetCPUCycle();
- tDelta = t1 - t0;
- CloseHandle(hMutex);
- EA::UnitTest::ReportVerbosity(1, "Windows HMUTEX time (ticks): %" PRIu64 "\n", tDelta);
- }
- }
- //////////////////////////////////////////////////
- //////////////////////////////////////////////////
- // Critical section
- {
- CRITICAL_SECTION cs;
- InitializeCriticalSection(&cs);
- EnterCriticalSection(&cs);
- LeaveCriticalSection(&cs);
- t0 = EA::StdC::Stopwatch::GetCPUCycle();
- for(i = 0; i < kLoopCount; i++)
- {
- EnterCriticalSection(&cs);
- LeaveCriticalSection(&cs);
- }
- t1 = EA::StdC::Stopwatch::GetCPUCycle();
- tDelta = t1 - t0;
- DeleteCriticalSection(&cs);
- EA::UnitTest::ReportVerbosity(1, "Windows CriticalSection time (ticks): %" PRIu64 "\n", tDelta);
- }
- //////////////////////////////////////////////////
- #endif
- return nErrorCount;
- }
- static int TestThreadFutexRegressions()
- {
- int nErrorCount(0);
- #if EA_THREADS_AVAILABLE && EATHREAD_DEBUG_FUTEX_HANG_ENABLED
- {
- // Test the ability of Futex to report the callstack of another thread holding a futex.
- struct FutexCallstackTestThread : public EA::Thread::IRunnable
- {
- ThreadParameters mThreadParams; //
- EA::Thread::Thread mThread; // The Thread object.
- EA::Thread::Futex* mpFutex; // A Futex.
- FutexCallstackTestThread() : mThreadParams(), mThread(), mpFutex(NULL) {}
- FutexCallstackTestThread(const FutexCallstackTestThread&){} // Avoid compiler warnings.
- void operator=(const FutexCallstackTestThread&){} // Avoid compiler warnings.
- intptr_t Run(void*)
- {
- if(EA::StdC::Strstr(mThreadParams.mpName, "0")) // If we are thread 0...
- {
- mpFutex->Lock();
- mpFutex->Lock();
- EA::Thread::ThreadSleep(4000);
- mpFutex->Unlock();
- mpFutex->Unlock();
- }
- else
- {
- mpFutex->Lock(); // This should result in a printf tracing two thread 0 lock callstacks.
- mpFutex->Unlock();
- }
- return 0;
- }
- };
- FutexCallstackTestThread thread[2];
- EA::Thread::Futex futex;
- for(int i = 0; i < 3; i++)
- {
- thread[0].mThreadParams.mpName = "FutexTest0";
- thread[0].mpFutex = &futex;
- thread[0].mThread.Begin(&thread[0], NULL, &thread[0].mThreadParams);
- EA::UnitTest::ThreadSleep(300);
- thread[1].mThreadParams.mpName = "FutexTest1";
- thread[1].mpFutex = &futex;
- thread[1].mThread.Begin(&thread[1], NULL, &thread[1].mThreadParams);
- EA::UnitTest::ThreadSleep(5000);
- for(int i = 0; i < 2; i++)
- thread[i].mThread.WaitForEnd();
- }
- }
- #endif
- return nErrorCount;
- }
- #define EATHREAD_DEBUG_FUTEX_HAMMER_ENABLED 1
- // Check multithreaded correctness of Futex.
- // Here we hammer on the futex via multiple threads while incrementing non atomic counter
- // if the lock ever fails the global count will be incorrect.
- volatile uint32_t gCommonCount = 0;
- static int TestThreadFutexHammer()
- {
- int nErrorCount(0);
- #if EA_THREADS_AVAILABLE && EATHREAD_DEBUG_FUTEX_HAMMER_ENABLED
- {
- static const int NUM_SPINNING_THREADS = 4;
- static const uint32_t MAX_NUM_LOOPS = 1 << 5;
- // Test the ability of Futex to report the callstack of another thread holding a futex.
- struct FutexCallstackTestThread : public EA::Thread::IRunnable
- {
- ThreadParameters mThreadParams; //
- EA::Thread::Thread mThread; // The Thread object.
- EA::Thread::Futex* mpFutex; // A Futex.
- uint32_t mThreadLocalId;
- FutexCallstackTestThread() : mThreadParams(), mThread(), mpFutex(NULL) {}
- FutexCallstackTestThread(const FutexCallstackTestThread&) {} // Avoid compiler warnings.
- void operator=(const FutexCallstackTestThread&) {} // Avoid compiler warnings.
- intptr_t Run(void*)
- {
- for (uint32_t i = 0; i < MAX_NUM_LOOPS; i++)
- {
- mpFutex->Lock();
- gCommonCount++;
- mpFutex->Unlock();
- }
- return 0;
- }
- };
- FutexCallstackTestThread thread[NUM_SPINNING_THREADS];
- EA::Thread::Futex futex;
- gCommonCount = 0; // for multiple runs
- for(int i = 0; i < NUM_SPINNING_THREADS; i++)
- {
- thread[i].mpFutex = &futex;
- thread[i].mThread.Begin(&thread[i], NULL, &thread[i].mThreadParams);
- }
- EA::UnitTest::ThreadSleep(13);
- for(int i = 0; i < NUM_SPINNING_THREADS; i++)
- thread[i].mThread.WaitForEnd();
- EATEST_VERIFY_MSG(gCommonCount == NUM_SPINNING_THREADS * MAX_NUM_LOOPS, "Multithreaded futex test failed, non atomic counter is incorrect");
- }
- #endif
- return nErrorCount;
- }
- ///////////////////////////////////////////////////////////////////////////////
- // TestThreadFutex
- //
- int TestThreadFutex()
- {
- int nErrorCount(0);
- nErrorCount += TestThreadFutexSingle();
- nErrorCount += TestThreadFutexSpeed();
- nErrorCount += TestThreadFutexRegressions();
- // hammer on the futex a few times
- for(int j=0; j <1;j++)
- {
- nErrorCount += TestThreadFutexHammer();
- }
- #if EA_THREADS_AVAILABLE
- nErrorCount += TestThreadFutexMulti(FutexTestThreadFunction1, 4);
- nErrorCount += TestThreadFutexMulti(FutexTestThreadFunction2, 4);
- nErrorCount += TestThreadFutexMulti(FutexTestThreadFunction3, 4);
- #endif
- return nErrorCount;
- }
|