|
|
@@ -0,0 +1,1345 @@
|
|
|
+///////////////////////////////////////////////////////////////////////////////
|
|
|
+// Copyright (c) Electronic Arts Inc. All rights reserved.
|
|
|
+///////////////////////////////////////////////////////////////////////////////
|
|
|
+
|
|
|
+#include "TestThread.h"
|
|
|
+#include <EATest/EATest.h>
|
|
|
+#include <EAStdC/EAStopwatch.h>
|
|
|
+#include <EAStdC/EASprintf.h>
|
|
|
+#include <EAStdC/EAString.h>
|
|
|
+#include <EASTL/set.h>
|
|
|
+#include <eathread/eathread_thread.h>
|
|
|
+#include <eathread/eathread_sync.h>
|
|
|
+#include <eathread/eathread_condition.h>
|
|
|
+
|
|
|
+#ifdef EA_PLATFORM_LINUX
|
|
|
+ #include <unistd.h>
|
|
|
+#endif
|
|
|
+
|
|
|
+#ifdef EA_PLATFORM_MICROSOFT
|
|
|
+ EA_DISABLE_ALL_VC_WARNINGS()
|
|
|
+ #include <Windows.h>
|
|
|
+ EA_RESTORE_ALL_VC_WARNINGS()
|
|
|
+#endif
|
|
|
+
|
|
|
+#include <atomic>
|
|
|
+
|
|
|
+using namespace EA::Thread;
|
|
|
+
|
|
|
+
|
|
|
+const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
|
|
|
+static unsigned int sThreadTestTimeMS = 2000; // We potentially change this value below.
|
|
|
+static AtomicInt32 sThreadCount = 0;
|
|
|
+static AtomicInt32 sShouldGo = 0;
|
|
|
+
|
|
|
+
|
|
|
+static intptr_t TestFunction1(void*)
|
|
|
+{
|
|
|
+ ThreadTime nTimeEnd = EA::Thread::GetThreadTime() + sThreadTestTimeMS;
|
|
|
+
|
|
|
+ while(EA::Thread::GetThreadTime() < nTimeEnd)
|
|
|
+ ThreadSleep();
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static intptr_t TestFunction3(void*)
|
|
|
+{
|
|
|
+ // This following code should produce NULL pointer access violation exception
|
|
|
+ // EA::UnitTest::ReportVerbosity(1, "Throw NULL pointer Exception.\n");
|
|
|
+ // char* pTest = NULL;
|
|
|
+ // *pTest = 1;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static intptr_t TestFunction4(void* arg)
|
|
|
+{
|
|
|
+ const intptr_t returnValue = (intptr_t)arg;
|
|
|
+
|
|
|
+ EA::UnitTest::ThreadSleepRandom(0, 5);
|
|
|
+
|
|
|
+ return returnValue;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#if !defined(EA_PLATFORM_MOBILE)
|
|
|
+ static intptr_t TestFunction6(void* arg)
|
|
|
+ {
|
|
|
+ const intptr_t returnValue = (intptr_t)arg;
|
|
|
+
|
|
|
+ sThreadCount++;
|
|
|
+
|
|
|
+ while(sShouldGo == 0)
|
|
|
+ ThreadSleep(10);
|
|
|
+
|
|
|
+ EA::UnitTest::ThreadSleepRandom(3, 8);
|
|
|
+
|
|
|
+ sThreadCount--;
|
|
|
+
|
|
|
+ return returnValue;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
+static intptr_t TestFunction7(void*)
|
|
|
+{
|
|
|
+ while(sShouldGo == 0)
|
|
|
+ ThreadSleep(10);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static intptr_t TestFunction8(void*)
|
|
|
+{
|
|
|
+ ThreadSleep(2000);
|
|
|
+
|
|
|
+ ++sShouldGo;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static intptr_t TestFunction9(void* arg)
|
|
|
+{
|
|
|
+ return (intptr_t)GetThreadProcessor();
|
|
|
+}
|
|
|
+
|
|
|
+static intptr_t TestFunction10(void* arg)
|
|
|
+{
|
|
|
+ ThreadSleep(10);
|
|
|
+ return TestFunction9(arg);
|
|
|
+}
|
|
|
+
|
|
|
+static intptr_t TestFunction12(void* arg)
|
|
|
+{
|
|
|
+ ThreadSleep(10);
|
|
|
+ return TestFunction9(arg);
|
|
|
+}
|
|
|
+
|
|
|
+static intptr_t TestFunction11(void* arg)
|
|
|
+{
|
|
|
+ int requestedCore = *(static_cast<int*>(arg));
|
|
|
+ int coreIndex = (requestedCore % EA::Thread::GetProcessorCount());
|
|
|
+
|
|
|
+ SetThreadAffinityMask(UINT64_C(1) << coreIndex); // set the highest processor available.
|
|
|
+
|
|
|
+ for (int retryCount = 100; retryCount && (GetThreadProcessor() != coreIndex); retryCount--)
|
|
|
+ ThreadSleep(1);
|
|
|
+
|
|
|
+ return (intptr_t)GetThreadProcessor();
|
|
|
+}
|
|
|
+
|
|
|
+static intptr_t TestFunction13(void* arg)
|
|
|
+{
|
|
|
+ ThreadSleep(10);
|
|
|
+ ThreadEnd(42); // 42 is a magic number we will verify gets passed through the user.
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static intptr_t TestFunction3ExceptionWrapper(RunnableFunction defaultRunnableFunction, void* pContext)
|
|
|
+{
|
|
|
+ return defaultRunnableFunction(pContext);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+class TestRunnable1 : public IRunnable
|
|
|
+{
|
|
|
+ intptr_t Run(void*)
|
|
|
+ {
|
|
|
+ const ThreadTime nTimeEnd = EA::Thread::GetThreadTime() + sThreadTestTimeMS;
|
|
|
+ while (EA::Thread::GetThreadTime() < nTimeEnd)
|
|
|
+ ThreadSleep();
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+} gTestRunnable1;
|
|
|
+
|
|
|
+
|
|
|
+class TestRunnable2 : public IRunnable
|
|
|
+{
|
|
|
+ intptr_t Run(void*)
|
|
|
+ {
|
|
|
+ const ThreadTime nTimeEnd = EA::Thread::GetThreadTime() + sThreadTestTimeMS;
|
|
|
+
|
|
|
+ while(EA::Thread::GetThreadTime() < nTimeEnd)
|
|
|
+ {
|
|
|
+ ThreadSleep();
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+} gTestRunnable2;
|
|
|
+
|
|
|
+static intptr_t TestRunnable3ExceptionWrapper(IRunnable* defaultRunnableFunction, void* pContext)
|
|
|
+{
|
|
|
+ return defaultRunnableFunction->Run(pContext);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+class TestRunnable3 : public IRunnable
|
|
|
+{
|
|
|
+ intptr_t Run(void*)
|
|
|
+ {
|
|
|
+ // This following code should produce NULL pointer access violation exception
|
|
|
+ // EA::UnitTest::ReportVerbosity(1, "Throw NULL pointer Exception.\n");
|
|
|
+ // char* pTest = NULL;
|
|
|
+ // *pTest = 1;
|
|
|
+ ThreadSleep(500);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+} gTestRunnable3;
|
|
|
+
|
|
|
+class TestRunnable4 : public IRunnable
|
|
|
+{
|
|
|
+ intptr_t Run(void*)
|
|
|
+ {
|
|
|
+ // IRunnable object that returns the thread id that executed on.
|
|
|
+ return TestFunction9(NULL);
|
|
|
+ }
|
|
|
+} gTestRunnable4;
|
|
|
+
|
|
|
+
|
|
|
+int TestThreadAffinityMask()
|
|
|
+{
|
|
|
+ int nErrorCount = 0;
|
|
|
+ const int MAX_ITERATIONS = 16;
|
|
|
+ const int nAvailableProcessors = EA::Thread::GetProcessorCount();
|
|
|
+
|
|
|
+ auto VERIFY_AFFINITY_RESULT = [&](intptr_t in_result, int count)
|
|
|
+ {
|
|
|
+#if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED
|
|
|
+ auto result = static_cast<int>(in_result);
|
|
|
+ EATEST_VERIFY_F(result == (count % nAvailableProcessors),
|
|
|
+ "Thread failure: SetAffinityMask not working properly. Thread ran on: %d/%d <=> Expected: %d\n",
|
|
|
+ result, nAvailableProcessors, (count % nAvailableProcessors));
|
|
|
+#endif
|
|
|
+ };
|
|
|
+
|
|
|
+ int count = MAX_ITERATIONS;
|
|
|
+ while(--count)
|
|
|
+ {
|
|
|
+ // Test Thread Affinity Masks (thread parameters)
|
|
|
+ Thread thread;
|
|
|
+ ThreadParameters params;
|
|
|
+
|
|
|
+ params.mnAffinityMask = INT64_C(1) << (count % nAvailableProcessors);
|
|
|
+ params.mnProcessor = kProcessorAny;
|
|
|
+ thread.Begin(TestFunction10, NULL, ¶ms);
|
|
|
+
|
|
|
+ intptr_t result = 0;
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000, &result);
|
|
|
+
|
|
|
+ VERIFY_AFFINITY_RESULT(result, count);
|
|
|
+ }
|
|
|
+
|
|
|
+ count = MAX_ITERATIONS;
|
|
|
+ while(--count)
|
|
|
+ {
|
|
|
+ // Test Thread Affinity Masks (thread object)
|
|
|
+ ThreadParameters params;
|
|
|
+ params.mnProcessor = kProcessorAny;
|
|
|
+
|
|
|
+ Thread thread;
|
|
|
+ thread.Begin(TestFunction12, NULL, ¶ms); // sleeps then grabs the current thread id.
|
|
|
+ thread.SetAffinityMask(INT64_C(1) << (count % nAvailableProcessors));
|
|
|
+
|
|
|
+ intptr_t result = 0;
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000, &result);
|
|
|
+
|
|
|
+ VERIFY_AFFINITY_RESULT(result, count);
|
|
|
+ }
|
|
|
+
|
|
|
+ count = MAX_ITERATIONS;
|
|
|
+ while(--count)
|
|
|
+ {
|
|
|
+ // Test Thread Affinity Masks (global functions)
|
|
|
+ ThreadParameters params;
|
|
|
+ params.mnProcessor = kProcessorAny;
|
|
|
+
|
|
|
+ Thread thread;
|
|
|
+ thread.Begin(TestFunction11, &count, ¶ms);
|
|
|
+
|
|
|
+ intptr_t result = 0;
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000, &result);
|
|
|
+
|
|
|
+ VERIFY_AFFINITY_RESULT(result, count);
|
|
|
+ }
|
|
|
+
|
|
|
+ count = MAX_ITERATIONS;
|
|
|
+ while(--count)
|
|
|
+ {
|
|
|
+ // Test Thread Affinity Masks (thread parameters) - For IRunnable variant of the Thread::Begin function
|
|
|
+ ThreadParameters params;
|
|
|
+ params.mnProcessor = kProcessorAny;
|
|
|
+ params.mnAffinityMask = INT64_C(1) << (count % nAvailableProcessors);
|
|
|
+ params.mnProcessor = kProcessorAny;
|
|
|
+
|
|
|
+ Thread thread;
|
|
|
+ thread.Begin(&gTestRunnable4, NULL, ¶ms); // sleeps then grabs the current thread id.
|
|
|
+
|
|
|
+ intptr_t result = 0;
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000, &result);
|
|
|
+
|
|
|
+ VERIFY_AFFINITY_RESULT(result, count);
|
|
|
+ }
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int TestThreadPriorities()
|
|
|
+{
|
|
|
+ int nErrorCount = 0;
|
|
|
+
|
|
|
+ if(!IsSuperUser())
|
|
|
+ {
|
|
|
+ EA::EAMain::Report("Skipping Thread Priority test because we don't have sufficient system priviliages.\n");
|
|
|
+ return nErrorCount;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify that thread priorities act as expected.
|
|
|
+ // Threads with higher priorities should execute instead of or before threads of lower priorities.
|
|
|
+ // On some platforms (e.g. Windows), lower priority threads do get some execution time, so we have to recognize that.
|
|
|
+
|
|
|
+ // Create 20 threads of very high priority, 20 threads of high priority, and 20 threads of regular priority.
|
|
|
+ // Start the 20 very high priority threads first.
|
|
|
+ // Wait a bit then start the other 40 threads.
|
|
|
+ // Quit all the very high priority threads.
|
|
|
+ // Wait a bit, while having the 40 threads measure how much time they execute.
|
|
|
+ // Quit the remaining 40 threads.
|
|
|
+ // Verify that the 20 high priority threads executed much more than the regular threads.
|
|
|
+
|
|
|
+ struct PriorityTestThread : public EA::Thread::IRunnable
|
|
|
+ {
|
|
|
+ EA::Thread::Thread mThread;
|
|
|
+ EA::Thread::ThreadParameters mParameters;
|
|
|
+ EA::Thread::Semaphore mSemaphore;
|
|
|
+ char mThreadName[16];
|
|
|
+ volatile uint64_t mCounter;
|
|
|
+ volatile bool mbShouldRun;
|
|
|
+ char mPadd[EA_CACHE_LINE_SIZE];// make sure that these structures end up on different cachelines
|
|
|
+
|
|
|
+ PriorityTestThread() : mThread(), mParameters(), mSemaphore(0), mCounter(0), mbShouldRun(true) {}
|
|
|
+ PriorityTestThread(const PriorityTestThread&){}
|
|
|
+ void operator=(const PriorityTestThread&){}
|
|
|
+
|
|
|
+ intptr_t Run(void*)
|
|
|
+ {
|
|
|
+ mSemaphore.Wait();
|
|
|
+
|
|
|
+ while(mbShouldRun)
|
|
|
+ {
|
|
|
+ //char buffer[64];
|
|
|
+ //EA::StdC::Sprintf(buffer, "%f", (double)(mCounter*1.23)); // Just waste time.
|
|
|
+ mCounter++;
|
|
|
+ EAReadBarrier();
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ if((EA::Thread::GetProcessorCount() >= 4))
|
|
|
+ {
|
|
|
+ const int kThreadCount = 4;
|
|
|
+ PriorityTestThread threadHighestPriority[kThreadCount];
|
|
|
+ PriorityTestThread threadRegularPriority[kThreadCount];
|
|
|
+ PriorityTestThread threadHighPriority[kThreadCount];
|
|
|
+ EA::StdC::LimitStopwatch limitStopwatch(EA::StdC::Stopwatch::kUnitsSeconds);
|
|
|
+ const EA::Thread::ThreadAffinityMask kCommonAffinityMask = 0xf; // first 4 cores only
|
|
|
+
|
|
|
+ EA::Thread::ThreadParameters commonParams;
|
|
|
+ commonParams.mbDisablePriorityBoost = true; // we can disable boosting if we want a better simulation of console-like behavior
|
|
|
+ commonParams.mnAffinityMask = kCommonAffinityMask;
|
|
|
+ commonParams.mnProcessor = kProcessorAny;
|
|
|
+
|
|
|
+#if defined(EA_PLATFORM_MICROSOFT)
|
|
|
+ // Due to how windows thread priorities work we need to further increase the thread priority of the high threads
|
|
|
+ // If this is not done the test will randomly have the regular priority have a higher count than the high threads.
|
|
|
+ static const int kHigherPriorityDelta = kThreadPriorityMax - 2;
|
|
|
+#else
|
|
|
+ static const int kHigherPriorityDelta = 0;
|
|
|
+#endif
|
|
|
+ EA::Thread::SetThreadPriority(EA::Thread::kThreadPriorityDefault + 3);
|
|
|
+ for (int i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ {
|
|
|
+ PriorityTestThread& highestThread = threadHighestPriority[i];
|
|
|
+ highestThread.mParameters = commonParams;
|
|
|
+ highestThread.mParameters.mnPriority = (EA::Thread::kThreadPriorityDefault + 2 + kHigherPriorityDelta);
|
|
|
+ EA::StdC::Sprintf(highestThread.mThreadName, "Highest%d", i);
|
|
|
+ highestThread.mParameters.mpName = highestThread.mThreadName;
|
|
|
+ highestThread.mThread.Begin(&highestThread, NULL, &highestThread.mParameters);
|
|
|
+ }
|
|
|
+ {
|
|
|
+ PriorityTestThread& regularThread = threadRegularPriority[i];
|
|
|
+ regularThread.mParameters = commonParams;
|
|
|
+ regularThread.mParameters.mnPriority = (EA::Thread::kThreadPriorityDefault);
|
|
|
+ EA::StdC::Sprintf(regularThread.mThreadName, "Reg%d", i);
|
|
|
+ regularThread.mParameters.mpName = regularThread.mThreadName;
|
|
|
+ regularThread.mThread.Begin(®ularThread, NULL, ®ularThread.mParameters);
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ PriorityTestThread& highThread = threadHighPriority[i];
|
|
|
+ highThread.mParameters = commonParams;
|
|
|
+ highThread.mParameters.mnPriority = (EA::Thread::kThreadPriorityDefault + 1 + kHigherPriorityDelta);
|
|
|
+ EA::StdC::Sprintf(highThread.mThreadName, "High%d", i);
|
|
|
+ highThread.mParameters.mpName = highThread.mThreadName;
|
|
|
+ highThread.mThread.Begin(&highThread, NULL, &highThread.mParameters);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ limitStopwatch.SetTimeLimit(1, true);
|
|
|
+ while(!limitStopwatch.IsTimeUp())
|
|
|
+ { /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ }
|
|
|
+
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ threadHighestPriority[i].mSemaphore.Post(1);
|
|
|
+ threadHighPriority[i].mSemaphore.Post(1);
|
|
|
+ threadRegularPriority[i].mSemaphore.Post(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ limitStopwatch.SetTimeLimit(3, true);
|
|
|
+ while(!limitStopwatch.IsTimeUp())
|
|
|
+ { /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ }
|
|
|
+
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ threadHighestPriority[i].mbShouldRun = false;
|
|
|
+ EAWriteBarrier();
|
|
|
+
|
|
|
+ limitStopwatch.SetTimeLimit(3, true);
|
|
|
+ while(!limitStopwatch.IsTimeUp())
|
|
|
+ { /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ }
|
|
|
+
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ threadHighPriority[i].mbShouldRun = false;
|
|
|
+ threadRegularPriority[i].mbShouldRun = false;
|
|
|
+ }
|
|
|
+ EAWriteBarrier();
|
|
|
+
|
|
|
+ limitStopwatch.SetTimeLimit(3, true);
|
|
|
+ while(!limitStopwatch.IsTimeUp())
|
|
|
+ { /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ }
|
|
|
+
|
|
|
+ EA::Thread::SetThreadPriority(EA::Thread::kThreadPriorityDefault);
|
|
|
+
|
|
|
+ uint64_t highPriorityCount = 0;
|
|
|
+ uint64_t regularPriorityCount = 0;
|
|
|
+
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ highPriorityCount += threadHighPriority[i].mCounter;
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ regularPriorityCount += threadRegularPriority[i].mCounter;
|
|
|
+
|
|
|
+ // Verify that the higher priority threads always got priority over the regular priority threads.
|
|
|
+ EATEST_VERIFY_F(highPriorityCount > regularPriorityCount, "Priority execution failure: highPriorityCount: %I64u, regularPriorityCount: %I64u", highPriorityCount, regularPriorityCount);
|
|
|
+
|
|
|
+ // Wait for the threads to end before continuing.
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ threadHighestPriority[i].mThread.WaitForEnd();
|
|
|
+ threadHighPriority[i].mThread.WaitForEnd();
|
|
|
+ threadRegularPriority[i].mThread.WaitForEnd();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+int TestSetThreadProcessConstants()
|
|
|
+{
|
|
|
+ int nErrorCount = 0;
|
|
|
+
|
|
|
+ // testing a user reported regression of negative value shifts
|
|
|
+ for(auto k : { kProcessorDefault, kProcessorAny })
|
|
|
+ {
|
|
|
+ Thread t;
|
|
|
+
|
|
|
+ t.Begin([](void* param) -> intptr_t
|
|
|
+ {
|
|
|
+ int kConstant = *(int*)param;
|
|
|
+ SetThreadProcessor(kConstant);
|
|
|
+ return 0;
|
|
|
+ }, &k);
|
|
|
+ t.WaitForEnd();
|
|
|
+ }
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+int TestNullThreadNames()
|
|
|
+{
|
|
|
+ int nErrorCount = 0;
|
|
|
+
|
|
|
+ {
|
|
|
+ ThreadParameters threadParams;
|
|
|
+ threadParams.mpName = nullptr;
|
|
|
+
|
|
|
+ Thread t;
|
|
|
+ t.Begin([](void*) -> intptr_t { return 0; }, NULL, &threadParams);
|
|
|
+ t.WaitForEnd();
|
|
|
+ }
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+int TestLambdaThreads()
|
|
|
+{
|
|
|
+ int nErrorCount = 0;
|
|
|
+
|
|
|
+ { // test rvalue
|
|
|
+ int foo = 0;
|
|
|
+ MakeThread([&]
|
|
|
+ {
|
|
|
+ EATEST_VERIFY(foo == 0);
|
|
|
+ foo = 42;
|
|
|
+ })
|
|
|
+ .WaitForEnd();
|
|
|
+ EATEST_VERIFY(foo == 42);
|
|
|
+ }
|
|
|
+
|
|
|
+ { // test lvalue
|
|
|
+ int foo = 0;
|
|
|
+
|
|
|
+ auto callme = [&]
|
|
|
+ {
|
|
|
+ EATEST_VERIFY(foo == 0);
|
|
|
+ foo = 42;
|
|
|
+ };
|
|
|
+
|
|
|
+ MakeThread(callme).WaitForEnd();
|
|
|
+ EATEST_VERIFY(foo == 42);
|
|
|
+ }
|
|
|
+
|
|
|
+ { // test thread parameters
|
|
|
+ const char* MY_THREAD_NAME = "my thread name";
|
|
|
+ ThreadParameters params;
|
|
|
+ params.mpName = MY_THREAD_NAME;
|
|
|
+
|
|
|
+ MakeThread(
|
|
|
+ [&] { EATEST_VERIFY(strncmp(MY_THREAD_NAME, GetThreadName(), EATHREAD_NAME_SIZE) == 0); }, params)
|
|
|
+ .WaitForEnd();
|
|
|
+ }
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+int TestThreadDynamicData()
|
|
|
+{
|
|
|
+ int nErrorCount = 0;
|
|
|
+
|
|
|
+ const int kOverflowDynamicDataCount = 256; // Must be greater than EA::Thread::kMaxThreadDynamicDataCount.
|
|
|
+
|
|
|
+ for(int i = 0; i < kOverflowDynamicDataCount; i++)
|
|
|
+ {
|
|
|
+ EA::Thread::ThreadId id = 0;
|
|
|
+ EA::Thread::SysThreadId sysId = 0;
|
|
|
+
|
|
|
+ MakeThread([&]
|
|
|
+ {
|
|
|
+ id = EA::Thread::GetThreadId();
|
|
|
+ sysId = EA::Thread::GetSysThreadId();
|
|
|
+ })
|
|
|
+ .WaitForEnd();
|
|
|
+
|
|
|
+ EATEST_VERIFY(FindThreadDynamicData(id) == nullptr);
|
|
|
+ EATEST_VERIFY(FindThreadDynamicData(sysId) == nullptr);
|
|
|
+ }
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+int TestSetThreadProcessor()
|
|
|
+{
|
|
|
+ // Exercise EA::Thread::GetThreadId, EA::Thread::GetSysThreadId, EAThreadGetUniqueId, SetThreadProcessor, GetThreadProcessor.
|
|
|
+
|
|
|
+ // Create and start N threads paused.
|
|
|
+ // Release all threads to run at once.
|
|
|
+ // Have each of the threads record its EAThreadGetUniqueId value and exit.
|
|
|
+ // Verify that there were no collisions in the recorded id values.
|
|
|
+ int nErrorCount = 0;
|
|
|
+
|
|
|
+ struct IdTestThread : public EA::Thread::IRunnable
|
|
|
+ {
|
|
|
+ EA::Thread::Thread mThread; // The Thread object.
|
|
|
+ EA::Thread::Semaphore mSemaphore; // Used to pause the thread after it starts.
|
|
|
+ EA::Thread::ThreadUniqueId mUniqueId; // The EAThreadUniqueId that this thread gets assigned by the OS.
|
|
|
+ EA::Thread::ThreadId mThreadId; // The EAThreadUniqueId that this thread gets assigned by the OS.
|
|
|
+ EA::Thread::SysThreadId mSysThreadId; // The EAThreadUniqueId that this thread gets assigned by the OS.
|
|
|
+ int mAssignedProcessorId; // The processor id that we ask the OS to run this thread on.
|
|
|
+ int mProcessorId; // The processor id that this thread gets assigned by the OS. Should equal mAssignedProcessorId.
|
|
|
+
|
|
|
+ IdTestThread() : mThread(), mSemaphore(0), mUniqueId(), mThreadId(), mSysThreadId(), mAssignedProcessorId(), mProcessorId() {}
|
|
|
+ IdTestThread(const IdTestThread&){} // Avoid compiler warnings.
|
|
|
+ void operator=(const IdTestThread&){} // Avoid compiler warnings.
|
|
|
+
|
|
|
+ intptr_t Run(void*)
|
|
|
+ {
|
|
|
+ mSemaphore.Wait();
|
|
|
+ EAThreadGetUniqueId(mUniqueId);
|
|
|
+ mThreadId = EA::Thread::GetThreadId();
|
|
|
+ mSysThreadId = EA::Thread::GetSysThreadId();
|
|
|
+ mProcessorId = EA::Thread::GetThreadProcessor();
|
|
|
+ EAWriteBarrier();
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ #if defined(EA_PLATFORM_DESKTOP)
|
|
|
+ const int kThreadCount = 100;
|
|
|
+ #elif (EA_PLATFORM_WORD_SIZE == 8)
|
|
|
+ const int kThreadCount = 50;
|
|
|
+ #else
|
|
|
+ const int kThreadCount = 16;
|
|
|
+ #endif
|
|
|
+ IdTestThread thread[kThreadCount];
|
|
|
+ ThreadParameters threadParams;
|
|
|
+ EA::UnitTest::RandGenT<int> random(EA::UnitTest::GetRandSeed());
|
|
|
+ const int processorCount = EA::Thread::GetProcessorCount();
|
|
|
+
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ threadParams.mnProcessor = random(processorCount);
|
|
|
+ threadParams.mpName = "IdTest";
|
|
|
+ thread[i].mAssignedProcessorId = threadParams.mnProcessor;
|
|
|
+ thread[i].mThread.Begin(&thread[i], NULL, &threadParams);
|
|
|
+ }
|
|
|
+
|
|
|
+ EA::UnitTest::ThreadSleep(1000);
|
|
|
+
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ thread[i].mSemaphore.Post(1);
|
|
|
+
|
|
|
+ EA::UnitTest::ThreadSleep(1000);
|
|
|
+
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ thread[i].mThread.WaitForEnd();
|
|
|
+ EAReadBarrier();
|
|
|
+
|
|
|
+ EA::Thread::ThreadUniqueId uniqueIdArray[kThreadCount];
|
|
|
+ EA::Thread::ThreadId idArray[kThreadCount];
|
|
|
+ EA::Thread::SysThreadId sysIdArray[kThreadCount];
|
|
|
+
|
|
|
+ // Problem: We don't have an EAThreadEqual(const ThreadId&, const ThreadId&) function, but could use one.
|
|
|
+ // If we had such a thing, then we wouldn't need the odd code below and could probably use an eastl::set.
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ memset(&uniqueIdArray[i], 0, sizeof(EA::Thread::ThreadUniqueId));
|
|
|
+ memset(&idArray[i], 0, sizeof(EA::Thread::ThreadId));
|
|
|
+ memset(&sysIdArray[i], 0, sizeof(EA::Thread::SysThreadId));
|
|
|
+ }
|
|
|
+
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ for(int j = 0; j < i; j++)
|
|
|
+ EATEST_VERIFY(memcmp(&uniqueIdArray[j], &thread[i].mUniqueId, sizeof(EA::Thread::ThreadUniqueId)) != 0);
|
|
|
+ uniqueIdArray[i] = thread[i].mUniqueId;
|
|
|
+
|
|
|
+ for(int j = 0; j < i; j++)
|
|
|
+ EATEST_VERIFY(memcmp(&idArray[j], &thread[i].mThreadId, sizeof(EA::Thread::ThreadId)) != 0);
|
|
|
+ idArray[i] = thread[i].mThreadId;
|
|
|
+
|
|
|
+ for(int j = 0; j < i; j++)
|
|
|
+ EATEST_VERIFY(memcmp(&sysIdArray[j], &thread[i].mSysThreadId, sizeof(EA::Thread::SysThreadId)) != 0);
|
|
|
+ sysIdArray[i] = thread[i].mSysThreadId;
|
|
|
+
|
|
|
+ // The following will fail on some platforms, as they don't support assigning
|
|
|
+ // thread affinity (e.g. PS3) or don't respect the assigned thread affinity (e.g. Windows).
|
|
|
+ // To consider: make a define which identifies which platforms rigidly follow thread processor assignments.
|
|
|
+ // On Windows, EAThread doesn't use SetThreadAffinityMask but rather uses SetThreadIdealProcessor,
|
|
|
+ // which doesn't guarantee which processor the thread will run on and rather is a hint.
|
|
|
+ #if defined(EA_PLATFORM_CONSOLE) && defined(EA_PLATFORM_MICROSOFT) // To do: add platforms to this list appropriately.
|
|
|
+ EATEST_VERIFY_F(thread[i].mProcessorId == thread[i].mAssignedProcessorId,
|
|
|
+ " Error: Thread assigned to run on processor %d, found to be running on processor %d.",
|
|
|
+ thread[i].mAssignedProcessorId, thread[i].mProcessorId);
|
|
|
+ #endif
|
|
|
+ }
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int TestThreadDisablePriorityBoost()
|
|
|
+{
|
|
|
+ int nErrorCount = 0;
|
|
|
+
|
|
|
+#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_CAPILANO)
|
|
|
+ {
|
|
|
+ Thread thread;
|
|
|
+ ThreadParameters params;
|
|
|
+
|
|
|
+ auto priorityBoostTester = [&](BOOL expectedDisablePriorityBoost)
|
|
|
+ {
|
|
|
+ thread.Begin(
|
|
|
+ [](void* pInExpectedDisablePriorityBoost) -> intptr_t
|
|
|
+ {
|
|
|
+ BOOL* pExpectedDisablePriorityBoost = (BOOL*)pInExpectedDisablePriorityBoost;
|
|
|
+ int nErrorCount = 0;
|
|
|
+ PBOOL pDisablePriorityBoost = nullptr;
|
|
|
+
|
|
|
+ auto result = GetThreadPriorityBoost(GetCurrentThread(), pDisablePriorityBoost);
|
|
|
+ EATEST_VERIFY_MSG(result != 0, "GetThreadPriorityBoost failed\n");
|
|
|
+ EATEST_VERIFY_MSG((result != 0) && *pDisablePriorityBoost == *pExpectedDisablePriorityBoost,
|
|
|
+ "Thread Priority Boost was not disabled\n");
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+ },
|
|
|
+ &expectedDisablePriorityBoost, ¶ms);
|
|
|
+
|
|
|
+ intptr_t threadErrorCount = 0;
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000, &threadErrorCount);
|
|
|
+ nErrorCount += (int)threadErrorCount;
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ params.mbDisablePriorityBoost = true;
|
|
|
+ priorityBoostTester(TRUE);
|
|
|
+
|
|
|
+ params.mbDisablePriorityBoost = false;
|
|
|
+ priorityBoostTester(FALSE);
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int TestThreadParameters()
|
|
|
+{ // Test ThreadParameters
|
|
|
+ int nErrorCount = 0;
|
|
|
+ Thread::Status status;
|
|
|
+ const int kThreadCount(kMaxConcurrentThreadCount - 1);
|
|
|
+ ThreadId threadId[kThreadCount];
|
|
|
+ Thread thread[kThreadCount];
|
|
|
+ int i;
|
|
|
+ ThreadParameters threadParameters;
|
|
|
+
|
|
|
+ const int nOriginalPriority = GetThreadPriority();
|
|
|
+
|
|
|
+ // Set our thread priority to match that of the threads we will be creating below.
|
|
|
+ SetThreadPriority(kThreadPriorityDefault + 1);
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ status = thread[i].GetStatus();
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusNone, "Thread failure: Thread should have kStatusNone (2).\n");
|
|
|
+
|
|
|
+ // ThreadParameters
|
|
|
+ threadParameters.mnStackSize = 32768;
|
|
|
+ threadParameters.mnPriority = kThreadPriorityDefault + 1;
|
|
|
+ threadParameters.mpName = "abcdefghijklmnopqrstuvwxyz"; // Make an overly large name.
|
|
|
+
|
|
|
+ threadId[i] = thread[i].Begin(TestFunction1, NULL, &threadParameters);
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(threadId[i] != (ThreadId)kThreadIdInvalid, "Thread failure: ThreadBegin failed.\n");
|
|
|
+
|
|
|
+ // It turns out that you can't really do such a thing as set lower priority in native Linux, not with SCHED_OTHER, at least.
|
|
|
+ #if ((!defined(EA_PLATFORM_UNIX) && !defined(__APPLE__)) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY
|
|
|
+ EATEST_VERIFY_MSG(thread[i].GetPriority() == threadParameters.mnPriority, "Thread failure: Thread Priority not set correctly (2).\n");
|
|
|
+
|
|
|
+ #endif
|
|
|
+
|
|
|
+ if(i > 0)
|
|
|
+ EATEST_VERIFY_MSG(threadId[i] != threadId[i-1], "Thread failure: Thread id collision (2).\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ ThreadSleep(200);
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ if(threadId[i] != kThreadIdInvalid)
|
|
|
+ thread[i].SetName("0123456789012345678901234567890"); // Make an overly large name.
|
|
|
+ }
|
|
|
+
|
|
|
+ ThreadSleep(200);
|
|
|
+
|
|
|
+ // It turns out that you can't really do such a thing as set lower priority in native Linux.
|
|
|
+ #if ((!defined(EA_PLATFORM_UNIX) || defined(__CYGWIN__)) && !defined(__APPLE__) && !EA_USE_CPP11_CONCURRENCY)
|
|
|
+ int nPriority;
|
|
|
+
|
|
|
+ if(threadId[0] != kThreadIdInvalid)
|
|
|
+ {
|
|
|
+ nPriority = thread[0].GetPriority();
|
|
|
+
|
|
|
+ thread[0].SetPriority(nPriority - 1);
|
|
|
+ EATEST_VERIFY_MSG(thread[0].GetPriority() == nPriority - 1, "Thread failure: Thread Priority not set correctly (3.1).\n");
|
|
|
+
|
|
|
+ thread[0].SetPriority(nPriority);
|
|
|
+ EATEST_VERIFY_MSG(thread[0].GetPriority() == nPriority, "Thread failure: Thread Priority not set correctly (3.2).\n");
|
|
|
+
|
|
|
+ }
|
|
|
+ #endif
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ if(threadId[i] != kThreadIdInvalid)
|
|
|
+ thread[i].WaitForEnd(GetThreadTime() + 30000);
|
|
|
+ }
|
|
|
+
|
|
|
+ SetThreadPriority(kThreadPriorityDefault);
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ status = thread[i].GetStatus();
|
|
|
+
|
|
|
+ if(threadId[i] != kThreadIdInvalid)
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (2).\n");
|
|
|
+ else
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusNone, "Thread failure: Thread should have kStatusNone (2).\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ SetThreadPriority(nOriginalPriority);
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+int TestThreadEnd()
|
|
|
+{
|
|
|
+ int nErrorCount(0);
|
|
|
+ EA::Thread::Thread thread;
|
|
|
+ Thread::Status status;
|
|
|
+
|
|
|
+ thread.Begin(TestFunction13);
|
|
|
+ ThreadSleep(20);
|
|
|
+
|
|
|
+ intptr_t returncode;
|
|
|
+ thread.WaitForEnd();
|
|
|
+ status = thread.GetStatus(&returncode);
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(returncode == 42, "Thread return code failure: Expected return code 42.");
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded.\n");
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+int TestThreadThread()
|
|
|
+{
|
|
|
+ int nErrorCount(0);
|
|
|
+
|
|
|
+ sThreadTestTimeMS = (gTestLengthSeconds * 1000) / 2; // '/2' because this test doesn't need so much time.
|
|
|
+
|
|
|
+ nErrorCount += TestThreadAffinityMask();
|
|
|
+ nErrorCount += TestThreadEnd();
|
|
|
+
|
|
|
+ {
|
|
|
+ ThreadId threadId = GetThreadId();
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "GetThreadId failure.\n");
|
|
|
+
|
|
|
+ SysThreadId sysThreadId = GetSysThreadId(threadId);
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(sysThreadId != kSysThreadIdInvalid, "GetSysThreadId failure.\n");
|
|
|
+
|
|
|
+ #if (defined(EA_PLATFORM_MICROSOFT) || defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_KETTLE)) && !EA_USE_CPP11_CONCURRENCY
|
|
|
+ const void* pStackBase = GetThreadStackBase();
|
|
|
+ const void* pStackTop = &pStackBase;
|
|
|
+ const intptr_t stackSize = ((char*)pStackBase - (char*)pStackTop);
|
|
|
+
|
|
|
+ // Verify that pStackBase is non-NULL and that the stack size is less than N MB.
|
|
|
+ EATEST_VERIFY_MSG(pStackBase && (stackSize < 10485760), "GetThreadStackBase failure.\n");
|
|
|
+
|
|
|
+ #endif
|
|
|
+
|
|
|
+ // We disable this test for now on Kettle because although we have 7 cores available
|
|
|
+ // there is no guaranty the their ID are 0..6 and the system takes core ID 7
|
|
|
+ #if !defined(EA_PLATFORM_KETTLE)
|
|
|
+
|
|
|
+ int processorCount = GetProcessorCount();
|
|
|
+ int processor = GetThreadProcessor();
|
|
|
+
|
|
|
+ // This isn't much of a test, but it at least exercizes the function.
|
|
|
+ EATEST_VERIFY_F(processor < processorCount, " Error: GetThreadProcessor [%d] >= GetProcessorCount [%d].\n", processor, processorCount);
|
|
|
+ #endif
|
|
|
+
|
|
|
+ // To do: Test this:
|
|
|
+ // void SetThreadProcessor(int nProcessor);
|
|
|
+ }
|
|
|
+
|
|
|
+ { // Test Current thread functionality
|
|
|
+ const int nOriginalPriority = GetThreadPriority();
|
|
|
+ int nPriority = kThreadPriorityDefault;
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(nPriority >= kThreadPriorityMin, "Thread priority failure (1).\n");
|
|
|
+ EATEST_VERIFY_MSG(nPriority <= kThreadPriorityMax, "Thread priority failure (2).\n");
|
|
|
+
|
|
|
+ // It turns out that you can't really do such a thing as set lower priority with most Unix threading subsystems.
|
|
|
+ // You can do so with Cygwin because it is just a pthreads API running on Windows OS/threading.
|
|
|
+ // C++11 thread libraries also provide no means to set or query thread priority.
|
|
|
+ #if (!defined(EA_PLATFORM_UNIX) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY
|
|
|
+ int nPriority1;
|
|
|
+ bool bResult;
|
|
|
+
|
|
|
+ bResult = SetThreadPriority(nPriority);
|
|
|
+ EATEST_VERIFY_MSG(bResult, "Thread priority failure (3).\n");
|
|
|
+
|
|
|
+ bResult = SetThreadPriority(nPriority - 1);
|
|
|
+ EATEST_VERIFY_MSG(bResult, "Thread priority failure (4).\n");
|
|
|
+
|
|
|
+ nPriority1 = GetThreadPriority();
|
|
|
+ EATEST_VERIFY_MSG(nPriority1 == nPriority - 1, "Thread priority failure (5).\n");
|
|
|
+
|
|
|
+ bResult = SetThreadPriority(nPriority + 1);
|
|
|
+ EATEST_VERIFY_MSG(bResult, "Thread priority failure (6).\n");
|
|
|
+
|
|
|
+ nPriority1 = GetThreadPriority();
|
|
|
+ EATEST_VERIFY_MSG(nPriority1 == nPriority + 1, "Thread priority failure (7).\n");
|
|
|
+
|
|
|
+ bResult = SetThreadPriority(kThreadPriorityDefault);
|
|
|
+ EATEST_VERIFY_MSG(bResult, "Thread priority failure (8).\n");
|
|
|
+
|
|
|
+ nPriority1 = GetThreadPriority();
|
|
|
+ EATEST_VERIFY_MSG(nPriority1 == kThreadPriorityDefault, "Thread priority failure (9).\n");
|
|
|
+
|
|
|
+ #endif
|
|
|
+
|
|
|
+ SetThreadPriority(nOriginalPriority);
|
|
|
+ ThreadSleep(kTimeoutImmediate);
|
|
|
+ ThreadSleep(500);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ #if defined(EA_PLATFORM_WINDOWS) && !EA_USE_CPP11_CONCURRENCY
|
|
|
+ { // Try to reproduce Windows problem with Thread::GetStatus returning kStatusEnded when it should return kStatusRunning.
|
|
|
+ // On my current work machine (WinXP32, Single P4 CPU) this problem doesn't occur. But it might occur with others.
|
|
|
+ Thread::Status status;
|
|
|
+ Thread threadBackground[8];
|
|
|
+ Thread thread;
|
|
|
+
|
|
|
+ for(int i = 0; i < 8; i++)
|
|
|
+ threadBackground[i].Begin(TestFunction1);
|
|
|
+
|
|
|
+ EA::Thread::SetThreadPriority(kThreadPriorityDefault + 2);
|
|
|
+ thread.Begin(TestFunction1);
|
|
|
+ status = thread.GetStatus();
|
|
|
+ EA::Thread::SetThreadPriority(kThreadPriorityDefault);
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusRunning, "Thread failure: Thread should have kStatusRunning.\n");
|
|
|
+
|
|
|
+ thread.WaitForEnd();
|
|
|
+ }
|
|
|
+ #endif
|
|
|
+
|
|
|
+
|
|
|
+ EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(TestFunction3ExceptionWrapper);
|
|
|
+ EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(TestRunnable3ExceptionWrapper);
|
|
|
+
|
|
|
+ { // Test thread creation functionality.
|
|
|
+ Thread::Status status;
|
|
|
+ const int kThreadCount(kMaxConcurrentThreadCount - 1);
|
|
|
+ Thread thread[kThreadCount];
|
|
|
+ ThreadId threadId[kThreadCount];
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ status = thread[i].GetStatus();
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusNone, "Thread failure: Thread should have kStatusNone (1).\n");
|
|
|
+
|
|
|
+ threadId[i] = thread[i].Begin(TestFunction1);
|
|
|
+ EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n");
|
|
|
+
|
|
|
+ threadId[i] = thread[i].Begin(TestFunction3);
|
|
|
+ EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction3) failed.\n");
|
|
|
+
|
|
|
+ threadId[i] = thread[i].Begin(&gTestRunnable3);
|
|
|
+ EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(&gTestRunnable3) failed.\n");
|
|
|
+
|
|
|
+ if(i > 0)
|
|
|
+ EATEST_VERIFY_MSG(threadId[i] != threadId[i-1], "Thread failure: Thread id collision (1).\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ // It turns out that you can't really do such a thing as set lower priority in native Linux.
|
|
|
+ // C++11 threads also have no support for priorities
|
|
|
+ #if (!defined(EA_PLATFORM_UNIX) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY
|
|
|
+ int nPriority;
|
|
|
+
|
|
|
+ nPriority = thread[0].GetPriority();
|
|
|
+ thread[0].SetPriority(nPriority - 1);
|
|
|
+
|
|
|
+ nPriority = thread[0].GetPriority();
|
|
|
+ thread[0].SetPriority(nPriority + 1);
|
|
|
+ #endif
|
|
|
+
|
|
|
+ ThreadSleep(200);
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ if(threadId[i] != kThreadIdInvalid)
|
|
|
+ thread[i].WaitForEnd(GetThreadTime() + 30000);
|
|
|
+ }
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ if(threadId[i] != kThreadIdInvalid)
|
|
|
+ {
|
|
|
+ status = thread[i].GetStatus();
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (1).\n");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ nErrorCount += TestThreadParameters();
|
|
|
+
|
|
|
+ {
|
|
|
+ // Test if we can set and retrieve a custom thread name
|
|
|
+ Thread::Status status;
|
|
|
+ Thread thread;
|
|
|
+ ThreadId threadId;
|
|
|
+ const char* threadName = "Test_Thread";
|
|
|
+ const char* defaultName = "DEFAULT";
|
|
|
+ ThreadParameters threadParameters;
|
|
|
+
|
|
|
+ threadParameters.mpName = defaultName;
|
|
|
+
|
|
|
+ sThreadTestTimeMS = 10000;
|
|
|
+ threadId = thread.Begin(TestFunction1, NULL, &threadParameters);
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n");
|
|
|
+ EATEST_VERIFY_MSG(strncmp(defaultName, thread.GetName(), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name used when initializing the thread.");
|
|
|
+
|
|
|
+ thread.SetName(threadName);
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(strncmp(threadName, thread.GetName(), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name set in SetName.");
|
|
|
+
|
|
|
+ ThreadSleep(sThreadTestTimeMS + 1000);
|
|
|
+
|
|
|
+ if(threadId != kThreadIdInvalid)
|
|
|
+ {
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000);
|
|
|
+ status = thread.GetStatus();
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (3).\n");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test the free standing function variant of the GetName/SetName API
|
|
|
+ {
|
|
|
+ // Test if we can set and retrieve a custom thread name
|
|
|
+ Thread::Status status;
|
|
|
+ Thread thread;
|
|
|
+ ThreadId threadId;
|
|
|
+ const char* threadName = "Test_Thread";
|
|
|
+ const char* defaultName = "DEFAULT";
|
|
|
+
|
|
|
+ ThreadParameters threadParameters;
|
|
|
+ threadParameters.mpName = defaultName;
|
|
|
+ threadParameters.mnProcessor = EA::Thread::kProcessorAny;
|
|
|
+ threadParameters.mnAffinityMask = EA::Thread::kThreadAffinityMaskAny;
|
|
|
+
|
|
|
+ static volatile std::atomic<bool> sbThreadStarted;
|
|
|
+ static volatile std::atomic<bool> sbThreadTestDone;
|
|
|
+ sbThreadStarted = false;
|
|
|
+ sbThreadTestDone = false;
|
|
|
+
|
|
|
+ threadId = thread.Begin( [](void*) -> intptr_t
|
|
|
+ {
|
|
|
+ sbThreadStarted = true;
|
|
|
+ while (!sbThreadTestDone)
|
|
|
+ ThreadSleep();
|
|
|
+ return 0;
|
|
|
+ },
|
|
|
+ NULL, &threadParameters);
|
|
|
+
|
|
|
+ while(!sbThreadStarted) // Wait for thread to start up
|
|
|
+ ThreadSleep();
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n");
|
|
|
+ EATEST_VERIFY_MSG(strncmp(defaultName, GetThreadName(threadId), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name used when initializing the thread.");
|
|
|
+
|
|
|
+ SetThreadName(threadId, threadName);
|
|
|
+ EATEST_VERIFY_MSG(strncmp(threadName, GetThreadName(threadId), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name set in SetName.");
|
|
|
+
|
|
|
+ sbThreadTestDone = true; // signal that test is completed and the thread can shutdown
|
|
|
+ if(threadId != kThreadIdInvalid)
|
|
|
+ {
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000);
|
|
|
+ status = thread.GetStatus();
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (3).\n");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ {
|
|
|
+ // Test the creation+destruction of many threads to make sure resources are recycled properly
|
|
|
+
|
|
|
+ #if defined(EA_PLATFORM_DESKTOP)
|
|
|
+ const int kThreadCount(200);
|
|
|
+ #else
|
|
|
+ const int kThreadCount(20);
|
|
|
+ #endif
|
|
|
+ Thread thread;
|
|
|
+ ThreadId threadId;
|
|
|
+ intptr_t returnValue;
|
|
|
+
|
|
|
+ EA::UnitTest::ReportVerbosity(1, "Creating many threads and then WaitForEnd()\n");
|
|
|
+
|
|
|
+ for(int i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ threadId = thread.Begin(TestFunction4, reinterpret_cast<void*>((uintptr_t)i));
|
|
|
+
|
|
|
+ Thread::Status status = thread.GetStatus(&returnValue);
|
|
|
+ if(status != Thread::kStatusEnded)
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000, &returnValue);
|
|
|
+ EA::UnitTest::ReportVerbosity(1, "Thread ended.\n");
|
|
|
+
|
|
|
+ EATEST_VERIFY_F(returnValue == i, "Thread failure: Thread return code is wrong (1). threadId: %s, status: %d, returnValue: %d\n", EAThreadThreadIdToString(threadId), (int)status, (int)returnValue);
|
|
|
+ if(returnValue != i)
|
|
|
+ EA::UnitTest::ReportVerbosity(1, " Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue);
|
|
|
+
|
|
|
+ // Get the status again to make sure it returns the correct status.
|
|
|
+ thread.GetStatus(&returnValue);
|
|
|
+ EATEST_VERIFY_F(returnValue == i, "Thread failure: Thread return code is wrong (2). threadId: %s, status: %d, returnValue: %d\n", EAThreadThreadIdToString(threadId), (int)status, (int)returnValue);
|
|
|
+ if(returnValue != i)
|
|
|
+ EA::UnitTest::ReportVerbosity(1, " Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ EA::UnitTest::ReportVerbosity(1, "Creating many threads and then repeat GetStatus() until they finish\n");
|
|
|
+ for(int i = 0; i < kThreadCount + 1; i++)
|
|
|
+ {
|
|
|
+ // Windows will get stuck in infinite loop if return value is 259 (STILL_ACTIVE)
|
|
|
+ if(i == 259)
|
|
|
+ ++i;
|
|
|
+
|
|
|
+ threadId = thread.Begin(TestFunction4, reinterpret_cast<void*>((uintptr_t)i));
|
|
|
+
|
|
|
+ const ThreadTime nGiveUpTime = EA::Thread::GetThreadTime() + 20000; // Give up after N milliseconds.
|
|
|
+
|
|
|
+ // Test to see if GetStatus() will recycle system resource properly
|
|
|
+ while(thread.GetStatus(&returnValue) != Thread::kStatusEnded)
|
|
|
+ {
|
|
|
+ ThreadSleep(100);
|
|
|
+
|
|
|
+ if(EA::Thread::GetThreadTime() > nGiveUpTime)
|
|
|
+ {
|
|
|
+ EA::UnitTest::ReportVerbosity(1, "Thread failure: GetStatus failed.\n");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(returnValue == i, "Thread failure: Thread return code is wrong (3).\n");
|
|
|
+ if(returnValue != i)
|
|
|
+ EA::UnitTest::ReportVerbosity(1, " Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue);
|
|
|
+
|
|
|
+ // Get the status again to make sure it returns the correct status.
|
|
|
+ thread.GetStatus(&returnValue);
|
|
|
+ EATEST_VERIFY_MSG(returnValue == i, "Thread failure: Thread return code is wrong (4).\n");
|
|
|
+ if(returnValue != i)
|
|
|
+ EA::UnitTest::ReportVerbosity(1, " Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue);
|
|
|
+
|
|
|
+ // See if calling WaitForEnd() now will result in a crash
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000, &returnValue);
|
|
|
+ EATEST_VERIFY_MSG(returnValue == i, "Thread failure: Thread return code is wrong (5).\n");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ {
|
|
|
+ // Test the creation of many threads
|
|
|
+
|
|
|
+ #if defined(EA_PLATFORM_DESKTOP)
|
|
|
+ Thread::Status status;
|
|
|
+ const int kThreadCount(96);
|
|
|
+ Thread thread[kThreadCount];
|
|
|
+ ThreadId threadId[kThreadCount];
|
|
|
+ int i;
|
|
|
+
|
|
|
+ sThreadTestTimeMS = 10000;
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ threadId[i] = thread[i].Begin(TestFunction1);
|
|
|
+ EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ ThreadSleep(sThreadTestTimeMS + 1000);
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ if(threadId[i] != kThreadIdInvalid)
|
|
|
+ thread[i].WaitForEnd(GetThreadTime() + 30000);
|
|
|
+ }
|
|
|
+
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ if(threadId[i] != kThreadIdInvalid)
|
|
|
+ {
|
|
|
+ status = thread[i].GetStatus();
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (3).\n");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ #endif
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ // Regression of Thread dtor behaviour - the dtor should not wait for created threads
|
|
|
+ // We reuse the atomic shouldgo to figure out what is really happening setting it to 0
|
|
|
+ // initially and then incrementing it when the thread callback completes, allowing
|
|
|
+ // us to detect whether the dtor completed before the thread callback.
|
|
|
+ sShouldGo = 0;
|
|
|
+ {
|
|
|
+ // Create a scope for our thread object
|
|
|
+ Thread thread;
|
|
|
+
|
|
|
+ // Get our thread going. It will sleep immediately for 2 seconds and then increment sShouldGo
|
|
|
+ ThreadId threadId = thread.Begin(TestFunction8);
|
|
|
+ EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: thread.Begin(TestFunction8) failed.\n");
|
|
|
+
|
|
|
+ // Give the thread a nominal second to get going and ensure we get a decent overlap of a second.
|
|
|
+ ThreadSleep(1000);
|
|
|
+
|
|
|
+ // Now we exit our scope while our thread in theory still has a second before it completes
|
|
|
+ }
|
|
|
+
|
|
|
+ // We either get here before the thread function completes in which case sShouldGo is 0 or
|
|
|
+ // we get here after it completes which would be incorrect and would give us a sShouldGo of 1
|
|
|
+ const bool threadScopeSemanticsVerified = sShouldGo == 0;
|
|
|
+
|
|
|
+ // Rather than try to wait on threadid in a cross platform way we simply sleep for 2 seconds which
|
|
|
+ // we know should be sufficient for the thread to complete. We can make this part of the test
|
|
|
+ // as shouldgo should be 1 when the thread has completed.
|
|
|
+ ThreadSleep(2000);
|
|
|
+
|
|
|
+ EATEST_VERIFY_MSG(threadScopeSemanticsVerified, "Thread failure: Thread should not have ended before Thread object dtor completed. Behaviour is inconsistent with intent.\n");
|
|
|
+ EATEST_VERIFY_MSG(sShouldGo == 1, "Thread failure: Thread set to run for 2 seconds at least 3 seconds ago. Should have ended by now.\n");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ {
|
|
|
+ int nOriginalPriority = EA::Thread::GetThreadPriority();
|
|
|
+ EA::Thread::SetThreadPriority(kThreadPriorityDefault);
|
|
|
+
|
|
|
+ // Tests setting and getting thread priority while the thread is active.
|
|
|
+ Thread::Status status;
|
|
|
+ Thread thread;
|
|
|
+ ThreadId threadId;
|
|
|
+ sShouldGo = 0;
|
|
|
+
|
|
|
+ threadId = thread.Begin(TestFunction7);
|
|
|
+ EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction7) failed.\n");
|
|
|
+
|
|
|
+ // It turns out that you can't really do such a thing as set lower priority in native Linux.
|
|
|
+ #if ((!defined(EA_PLATFORM_LINUX) && !defined(__APPLE__) && !defined(EA_PLATFORM_BSD)) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY
|
|
|
+ int nPriority = thread.GetPriority();
|
|
|
+
|
|
|
+ thread.SetPriority(nPriority - 1);
|
|
|
+ if(EATEST_VERIFY_MSG(thread.GetPriority() == nPriority - 1, "Thread failure: Thread Priority not set correctly (2).\n"))
|
|
|
+ nErrorCount++;
|
|
|
+
|
|
|
+ thread.SetPriority(nPriority);
|
|
|
+ EATEST_VERIFY_MSG(thread.GetPriority() == nPriority, "Thread failure: Thread Priority not set correctly (3).\n");
|
|
|
+ #endif
|
|
|
+
|
|
|
+ sShouldGo = 1;
|
|
|
+ thread.WaitForEnd(GetThreadTime() + 30000);
|
|
|
+
|
|
|
+ status = thread.GetStatus();
|
|
|
+ EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded.\n");
|
|
|
+ EA::Thread::SetThreadPriority(nOriginalPriority);
|
|
|
+ }
|
|
|
+
|
|
|
+ nErrorCount += TestThreadDynamicData();
|
|
|
+ nErrorCount += TestThreadPriorities();
|
|
|
+ nErrorCount += TestSetThreadProcessor();
|
|
|
+ nErrorCount += TestSetThreadProcessConstants();
|
|
|
+ nErrorCount += TestNullThreadNames();
|
|
|
+ nErrorCount += TestLambdaThreads();
|
|
|
+
|
|
|
+ {
|
|
|
+ // Test SetDefaultProcessor
|
|
|
+ Thread::SetDefaultProcessor(kProcessorAny);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ #if !defined(EA_PLATFORM_MOBILE)
|
|
|
+ // This test does not execute correctly on Android. The 'while(sThreadCount
|
|
|
+ // < kThreadCount)' loop sometimes hangs indefinitely because the spawned
|
|
|
+ // threads exit often before the loop continues, so the spawned
|
|
|
+ // thread count never gets up to kThreadCount. I guess the inner loop
|
|
|
+ // should be testing addedThreadCount too. (?) But, just disable for now,
|
|
|
+ // since I guess it's working on other platforms.
|
|
|
+ {
|
|
|
+ // Test very many threads starting and quitting, recycling
|
|
|
+ // The PS3 code had problems with leaking threads in this case.
|
|
|
+ // Create a bunch of threads.
|
|
|
+ // Every N ms quit one of them and create a new one.
|
|
|
+
|
|
|
+ // This test tends to be quite taxing on system resources, so cut it back for
|
|
|
+ // certain platforms. To do: use EATest's platform speed metrics.
|
|
|
+ const int kThreadCount(48); // This needs to be greater than the eathread_thread.cpp kMaxThreadDynamicDataCount value.
|
|
|
+ const int kTotalThreadsToRun(500);
|
|
|
+
|
|
|
+ Thread thread;
|
|
|
+ ThreadId threadId;
|
|
|
+ int addedThreadCount = 0;
|
|
|
+ int i = 0;
|
|
|
+
|
|
|
+ sThreadCount = 0;
|
|
|
+ sShouldGo = 0;
|
|
|
+
|
|
|
+ // Create threads.
|
|
|
+ for(i = 0; i < kThreadCount; i++)
|
|
|
+ {
|
|
|
+ threadId = thread.Begin(TestFunction6, reinterpret_cast<void*>((uintptr_t)i));
|
|
|
+ EATEST_VERIFY(threadId != kThreadIdInvalid);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Wait until they are all created and started.
|
|
|
+ while(sThreadCount < kThreadCount)
|
|
|
+ ThreadSleep(500);
|
|
|
+
|
|
|
+ // Let them run and exit as they go.
|
|
|
+ sShouldGo = 1;
|
|
|
+
|
|
|
+ // Add new threads as existing ones leave.
|
|
|
+ while(addedThreadCount < kTotalThreadsToRun)
|
|
|
+ {
|
|
|
+ if(sThreadCount < kThreadCount)
|
|
|
+ {
|
|
|
+ threadId = thread.Begin(TestFunction6, reinterpret_cast<void*>((uintptr_t)i++));
|
|
|
+
|
|
|
+ EATEST_VERIFY(threadId != kThreadIdInvalid);
|
|
|
+
|
|
|
+ addedThreadCount++;
|
|
|
+
|
|
|
+ #if defined(EA_PLATFORM_DESKTOP)
|
|
|
+ // The created threads will not get any time slices on weaker platforms.
|
|
|
+ // thus making the test create threads until the system is out of resources
|
|
|
+ ThreadSleep(kTimeoutYield);
|
|
|
+
|
|
|
+ // Sometimes it will never exit this loop.. because it will leave faster then it is made
|
|
|
+ if(addedThreadCount >= kTotalThreadsToRun)
|
|
|
+ break;
|
|
|
+ #endif
|
|
|
+ }
|
|
|
+ else
|
|
|
+ ThreadSleep(10);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Wait until they have all exited.
|
|
|
+ while(sThreadCount != 0) // While there are threads...
|
|
|
+ ThreadSleep(500);
|
|
|
+
|
|
|
+ // On some platforms it seems that thread entry/exit is so slow that the thread count might not necessarily
|
|
|
+ // reflect the actual number of threads running. This was causing an issue where the function was
|
|
|
+ // exiting before all threads completed, so I added this wait. It may be worth considering
|
|
|
+ // keeping track of all created threads in an array, and then using a thread-join operation here
|
|
|
+ // instead
|
|
|
+ ThreadSleep(1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endif
|
|
|
+
|
|
|
+ return nErrorCount;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|