Browse Source

adding tests

Roberto Parolin 6 years ago
parent
commit
a11b9f23ae
34 changed files with 9461 additions and 0 deletions
  1. 27 0
      LICENSE
  2. 143 0
      test/dllsafety/source/TestDllSafety.cpp
  3. 157 0
      test/interprocess_test/source/TestThreadInterprocess.cpp
  4. 17 0
      test/interprocess_test/source/TestThreadInterprocess.h
  5. 259 0
      test/interprocess_test/source/TestThreadInterprocessRWMutex.cpp
  6. 92 0
      test/performance/source/PerfTestThread.cpp
  7. 49 0
      test/performance/source/PerfTestThread.h
  8. 129 0
      test/performance/source/PerfTestThreadAtomic.cpp
  9. 392 0
      test/performance/source/PerfTestThreadSemaphore.cpp
  10. 1313 0
      test/thread/lib/pthreads-win32/include/pthread.h
  11. 174 0
      test/thread/lib/pthreads-win32/include/sched.h
  12. 163 0
      test/thread/lib/pthreads-win32/include/semaphore.h
  13. BIN
      test/thread/lib/pthreads-win32/lib/pthreadVC.dll
  14. BIN
      test/thread/lib/pthreads-win32/lib/pthreadVC.lib
  15. BIN
      test/thread/lib/pthreads-win32/lib/pthreadVC.pdb
  16. 242 0
      test/thread/source/TestEnumerateThreads.cpp
  17. 223 0
      test/thread/source/TestThread.cpp
  18. 46 0
      test/thread/source/TestThread.h
  19. 1007 0
      test/thread/source/TestThreadAtomic.cpp
  20. 157 0
      test/thread/source/TestThreadBarrier.cpp
  21. 391 0
      test/thread/source/TestThreadCallstack.cpp
  22. 335 0
      test/thread/source/TestThreadCondition.cpp
  23. 607 0
      test/thread/source/TestThreadFutex.cpp
  24. 233 0
      test/thread/source/TestThreadMutex.cpp
  25. 217 0
      test/thread/source/TestThreadRWMutex.cpp
  26. 259 0
      test/thread/source/TestThreadRWSemaLock.cpp
  27. 477 0
      test/thread/source/TestThreadRWSpinLock.cpp
  28. 353 0
      test/thread/source/TestThreadSemaphore.cpp
  29. 217 0
      test/thread/source/TestThreadSmartPtr.cpp
  30. 75 0
      test/thread/source/TestThreadSpinLock.cpp
  31. 236 0
      test/thread/source/TestThreadStorage.cpp
  32. 26 0
      test/thread/source/TestThreadSync.cpp
  33. 1345 0
      test/thread/source/TestThreadThread.cpp
  34. 100 0
      test/thread/source/TestThreadThreadPool.cpp

+ 27 - 0
LICENSE

@@ -0,0 +1,27 @@
+/*
+Copyright (C) 2017 Electronic Arts Inc.  All rights reserved.
+  
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+  
+1.  Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+2.  Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+3.  Neither the name of Electronic Arts, Inc. ("EA") nor the names of
+    its contributors may be used to endorse or promote products derived
+    from this software without specific prior written permission.
+  
+THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/

+ 143 - 0
test/dllsafety/source/TestDllSafety.cpp

@@ -0,0 +1,143 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include <EATest/EATest.h>
+#include <eathread/eathread.h>
+#include <eathread/eathread_atomic.h>
+#include <eathread/eathread_thread.h>
+#include <EAStdC/EAString.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <EAMain/EAEntryPointMain.inl>
+
+
+///////////////////////////////////////////////////////////////////////////////
+// operator new 
+// EASTL requires the following new operators to be defined.
+//
+void* operator new[](size_t size, const char*, int, unsigned, const char*, int)
+{
+	return new char[size];
+}
+
+void* operator new[](size_t size, size_t, size_t, const char*, int, unsigned, const char*, int)
+{
+	return new char[size];
+}
+
+const int NUM_THREADS = 5;
+
+///////////////////////////////////////////////////////////////////////////////
+// Structure where a single instance is passed to every active thread.
+//
+struct GlobalData
+{
+	EA::Thread::AtomicInt32 i;    
+	GlobalData()
+	: i(0)    
+	{}
+};
+
+typedef void (*DLL_ENTRY)(GlobalData*);
+
+///////////////////////////////////////////////////////////////////////////////
+// Thread Entry 
+//
+intptr_t ThreadFunction(void* pData)
+{
+	GlobalData* const pGlobalData = static_cast<GlobalData*>(pData);
+	while(!pGlobalData->i)
+	{
+		EA::Thread::ThreadSleep(50);
+	}
+
+	return 0;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// The function export below MUST be here as it forces lib files to be 
+// generated for the DLL's being built.
+//
+extern "C"
+EA_EXPORT void ForceLibFilesToBeGenerated(GlobalData* pGlobal)
+{
+	using namespace EA::Thread;
+	using namespace EA::UnitTest;
+
+	// Start two threads in this DLL instance
+	Thread thread;
+	for(size_t i = 0; i < NUM_THREADS; i++)
+		thread.Begin(ThreadFunction, pGlobal);
+			   
+	// Get the number of threads in the system 
+	ThreadEnumData enumData[32];
+	size_t count = EnumerateThreads(enumData, EAArrayCount(enumData));
+	//Report("Number of DLL threads detected:  %d\n", count);
+
+	for(size_t i = 0; i < count; i++)
+		enumData[i].Release();    
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Main
+//
+int EAMain(int argc, char** argv)
+{
+	using namespace EA::Thread;
+	using namespace EA::UnitTest;
+
+	int  nErrorCount = 0;    
+	GlobalData data;
+
+// TODO:  DLL usage must be made portable through support at various levels of our technology stack.
+#if defined(EA_PLATFORM_MICROSOFT) && defined(EA_PLATFORM_WIN32)
+	HINSTANCE__* mod1 = LoadLibrary("EAThreadTestDllSafetyMod1.dll");
+	HINSTANCE__* mod2 = LoadLibrary("EAThreadTestDllSafetyMod2.dll");
+	DLL_ENTRY dllmain1 = (DLL_ENTRY)GetProcAddress(mod1, "ForceLibFilesToBeGenerated");
+	DLL_ENTRY dllmain2 = (DLL_ENTRY)GetProcAddress(mod2, "ForceLibFilesToBeGenerated");
+
+	if(dllmain1 != NULL) 
+		dllmain1(&data);
+
+	if(dllmain2 != NULL) 
+		dllmain2(&data);
+#endif
+
+	EA::EAMain::PlatformStartup();
+	{
+		// Start n threads in this DLL instance
+		Thread thread;
+		for(size_t i = 0; i < NUM_THREADS; i++)
+			thread.Begin(ThreadFunction, &data);
+			   
+		// Get the number of threads in the system and validate.
+		ThreadEnumData enumData[32];
+		size_t count = EnumerateThreads(enumData, EAArrayCount(enumData));
+		Report("Number of threads detected:  %d/%d. \n", count, NUM_THREADS*3);
+		EATEST_VERIFY_MSG(count >= NUM_THREADS, "Thread tracking data isn't DLL safe.  We are missing data generated in other DLL's.");
+
+		for(size_t i = 0; i < count; i++)
+			enumData[i].Release();
+		
+		// Release the threads
+		data.i++;
+	}
+	EA::EAMain::PlatformShutdown(nErrorCount);
+
+#if defined(EA_PLATFORM_MICROSOFT) && defined(EA_PLATFORM_WIN32)
+	FreeLibrary(mod1); 
+	FreeLibrary(mod2);
+#endif
+
+	return nErrorCount;
+}
+
+
+
+
+
+

+ 157 - 0
test/interprocess_test/source/TestThreadInterprocess.cpp

@@ -0,0 +1,157 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThreadInterprocess.h"
+#include <EATest/EATest.h>
+#include <EAMain/EAMain.h>
+#include <eathread/eathread.h>
+#include <EAStdC/EAString.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <EAMain/EAEntryPointMain.inl>
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Globals
+//
+unsigned int gTestThreadCount   =  4;
+unsigned int gTestLengthSeconds = 10;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// EAThreadFailure
+//
+// This is called by EAThread's assert failure function.
+//
+static void EAThreadFailure(const char* pMessage, void* /*pContext*/)
+{
+	EA::UnitTest::Report("Thread test failure (EAThread assert): %s\n", pMessage);
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// operator new 
+// EASTL requires the following new operators to be defined.
+//
+void* operator new[](size_t size, const char*, int, unsigned, const char*, int)
+{
+	return new char[size];
+}
+
+void* operator new[](size_t size, size_t, size_t, const char*, int, unsigned, const char*, int)
+{
+	return new char[size];
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// main
+//
+int EAMain(int argc, char** argv)
+{
+	using namespace EA::Thread;
+	using namespace EA::UnitTest;
+	using namespace EA::StdC;
+
+	int  nErrorCount = 0;
+	bool bDebugMode  = false;
+
+	EA::EAMain::PlatformStartup();
+
+	// Process possible command line parameters
+	for(int i(0); i < argc; i++)
+	{
+		// Look for -?
+		if(Stricmp(argv[i], "-?") == 0)
+		{
+			printf("Command line parameters:\n");
+			printf("    -?            Get Help.\n");
+			printf("    -t <seconds>  Run the tests for <seconds> seconds each.\n");
+			printf("    -c <count>    Specifies test thread count.\n");
+			printf("    -d            Debug mode. Causes app to wait for a debugger to connect.\n");
+			continue;
+		}
+
+		// Run the tests for <seconds> seconds each.
+		if((Stricmp(argv[i], "-t") == 0) && (i < (argc - 1)))
+		{
+			gTestLengthSeconds = (unsigned int) atoi(argv[i+1]);
+			if(gTestLengthSeconds < 3)
+				gTestLengthSeconds = 3;
+			continue;
+		}
+
+		// Specifies test thread count. e.g. -c 10
+		if((Stricmp(argv[i], "-c") == 0) && (i < (argc - 1)))
+		{
+			gTestThreadCount = (unsigned int) atoi(argv[i+1]);
+			if(gTestThreadCount < 1)
+				gTestThreadCount = 1;
+			if(gTestThreadCount > 100)
+				gTestThreadCount = 100;
+			continue;
+		}
+
+		// Debug mode. Causes app to wait for a debugger to connect.
+		if(Stricmp(argv[i], "-d") == 0)
+		{
+			bDebugMode = true;
+			continue;
+		}
+	}
+
+
+	// Set EAThread to route its errors to our own error reporting function.
+	EA::Thread::SetAssertionFailureFunction(EAThreadFailure, NULL);
+
+	ReportVerbosity(1, "Test time seconds: %u\n", gTestLengthSeconds);
+	ReportVerbosity(1, "Thread count: %u\n", gTestThreadCount);
+
+	// Print ThreadId for this primary thread.
+	const ThreadId threadId = GetThreadId();
+	ReportVerbosity(1, "Primary thread ThreadId: %08x\n", (int)(intptr_t)threadId);
+
+	// Print SysThreadId for this primary thread.
+	const SysThreadId sysThreadId = GetSysThreadId(threadId);
+	ReportVerbosity(1, "Primary thread SysThreadId: %08d\n", (int)(intptr_t)sysThreadId);
+
+	// Print thread priority for this primary thread.
+	const int nPriority = EA::Thread::GetThreadPriority();
+	ReportVerbosity(1, "Primary thread priority: %d\n", nPriority);
+
+	const int nProcessorCount = EA::Thread::GetProcessorCount();
+	ReportVerbosity(1, "Currently active virtual processor count: %d\n", nProcessorCount);
+
+	if(bDebugMode)
+	{
+		Report("Debug mode activated. Waiting for debugger to attach.\n");
+		while(bDebugMode)
+			ThreadSleepRandom(500, 500, true);
+		Report("Continuing.\n");
+	}
+
+	// Add the tests
+	TestApplication testSuite("EAThread Interprocess Unit Tests", argc, argv);
+
+	testSuite.AddTest("RWMutex", TestThreadRWMutex);
+
+	nErrorCount += testSuite.Run();
+
+	EA::EAMain::PlatformShutdown(nErrorCount);
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+

+ 17 - 0
test/interprocess_test/source/TestThreadInterprocess.h

@@ -0,0 +1,17 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TESTTHREADINTERPROCESS_H
+#define TESTTHREADINTERPROCESS_H
+
+
+extern unsigned int gTestThreadCount;
+extern unsigned int gTestLengthSeconds;
+
+
+// Individual test functions
+int TestThreadRWMutex();
+
+
+#endif // Header include guard

+ 259 - 0
test/interprocess_test/source/TestThreadInterprocessRWMutex.cpp

@@ -0,0 +1,259 @@
+///////////////////////////////////////////////////////////////////////////////
+// TestThreadInterprocessRWMutex.cpp
+//
+// Copyright (c) 2009, Electronic Arts Inc. All rights reserved.
+// Created by Paul Pedriana
+///////////////////////////////////////////////////////////////////////////////
+
+
+#include <EATest/EATest.h>
+#include <eathread/eathread.h>
+#include <eathread/eathread_thread.h>
+#include <eathread/eathread_atomic.h>
+#include <eathread/eathread_rwmutex_ip.h>
+#include "TestThreadInterprocess.h"
+#include <stdlib.h>
+
+
+struct RWMWorkDataInterProcess
+{
+	volatile int mnExpectedValue;     // Intentionally not an atomic variable.
+	volatile int mnCalculatedValue;   // Intentionally not an atomic variable.
+	volatile int mnWriteLockCount;    // How many times the write lock was owned, across all processes.
+
+	RWMWorkDataInterProcess()
+	  : mnExpectedValue(0),
+		mnCalculatedValue(0),
+		mnWriteLockCount(0)
+	{
+		printf("RWMWorkDataInterProcess\n");
+	}
+
+   ~RWMWorkDataInterProcess()
+	{
+		printf("~RWMWorkDataInterProcess\n");
+	}
+};
+
+
+struct RWMWorkDataInterThread
+{
+	volatile bool           mbShouldQuit;           // 
+	EA::Thread::RWMutexIP   mRWMutexIP;             // 
+	EA::Thread::AtomicInt32 mnThreadIndex;          // 
+	EA::Thread::AtomicInt32 mnErrorCount;           // 
+	EA::Thread::AtomicInt32 mnReadLockCount;        // How many times the read lock was owned, within this process.
+	EA::Thread::AtomicInt32 mnWriteLockCount;       // How many times the write lock was owned, within this process.
+
+	RWMWorkDataInterThread() 
+	  : mbShouldQuit(false),
+		mRWMutexIP(NULL, false),
+		mnThreadIndex(0),
+		mnErrorCount(0),
+		mnReadLockCount(0),
+		mnWriteLockCount(0)
+	{
+	}
+
+protected:
+	RWMWorkDataInterThread(const RWMWorkDataInterThread& rhs);
+	RWMWorkDataInterThread& operator=(const RWMWorkDataInterThread& rhs);
+};
+
+
+static intptr_t RWThreadFunction(void* pvWorkData)
+{
+	using namespace EA::Thread;
+
+	int                     nErrorCount = 0;
+	RWMWorkDataInterThread* pWorkData   = (RWMWorkDataInterThread*)pvWorkData;
+	ThreadId                threadId    = GetThreadId();
+
+	EA::UnitTest::ReportVerbosity(1, "RWMutexIP test function created: %08x\n", (int)(intptr_t)threadId);
+
+	// We use the interprocess mutex to control access to an interprocess data struct.
+	Shared<RWMWorkDataInterProcess> gSharedData("RWMWorkDataIP");
+
+	// We track the amount of time we spend waiting for Locks.
+	//ThreadTime nInitialTime, nFinalTime;
+	const ThreadTime kMaxExpectedTime = 1000;
+
+	while(!pWorkData->mbShouldQuit)
+	{
+		const bool bWriteLock((rand() % 10) == 0);   // 10% of the time, do a write lock.
+
+		if(bWriteLock)
+		{
+			//nInitialTime = EA::Thread::GetThreadTime();
+
+			int nLockResult = pWorkData->mRWMutexIP.Lock(RWMutexIP::kLockTypeWrite, GetThreadTime() + kMaxExpectedTime);
+			EATEST_VERIFY_MSG(nLockResult != RWMutexIP::kResultError, "RWMutexIP failure: write lock.");
+
+			//nFinalTime = EA::Thread::GetThreadTime();
+			//EATEST_VERIFY_MSG((nFinalTime - nInitialTime) < kMaxExpectedTime, "RWMutexIP failure: write lock slow.");
+
+			if(nLockResult > 0)
+			{
+				gSharedData->mnWriteLockCount++;
+				pWorkData->mnWriteLockCount++;
+
+				// Verify exactly one write lock is set.
+				nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeWrite);
+				EATEST_VERIFY_MSG(nLockResult == 1, "RWMutexIP failure: write lock verify 1.");
+
+				// What we do here is spend some time manipulating mnExpectedValue and mnCalculatedValue
+				// while we have the write lock. We change their values in a predicable way but before 
+				// we are done mnCalculatedValue has been incremented by one and both values are equal.
+				const uintptr_t x = (uintptr_t)pWorkData;
+
+				gSharedData->mnExpectedValue    = -1;
+				EA::UnitTest::ThreadSleepRandom(10, 20);
+				gSharedData->mnCalculatedValue *= 50;
+				EA::UnitTest::ThreadSleepRandom(10, 20);
+				gSharedData->mnCalculatedValue /= (int)(((x + 1) / x) * 50); // This will always be the same as simply '/= 50'.
+				EA::UnitTest::ThreadSleepRandom(10, 20);
+				gSharedData->mnCalculatedValue += 1;
+				EA::UnitTest::ThreadSleepRandom(10, 20);
+				gSharedData->mnExpectedValue    = gSharedData->mnCalculatedValue;
+
+				// Verify no read locks are set.
+				nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeRead);
+				EATEST_VERIFY_MSG(nLockResult == 0, "RWMutexIP failure: write lock verify 2.");
+
+				// Verify exactly one write lock is set.
+				nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeWrite);
+				EATEST_VERIFY_MSG(nLockResult == 1, "RWMutexIP failure: write lock verify 3.");
+
+				// Verify there are now zero write locks set.
+				nLockResult = pWorkData->mRWMutexIP.Unlock();
+				EATEST_VERIFY_MSG(nLockResult == 0, "RWMutexIP failure: write unlock.");
+
+				EA::UnitTest::ThreadSleepRandom(40, 80);
+			}
+		}
+		else
+		{
+			const int nRecursiveLockCount(rand() % 2);
+			int i, nLockResult, nLocks = 0;
+
+			for(i = 0; i < nRecursiveLockCount; i++)
+			{
+				//nInitialTime = EA::Thread::GetThreadTime();
+
+				nLockResult = pWorkData->mRWMutexIP.Lock(RWMutexIP::kLockTypeRead, GetThreadTime() + kMaxExpectedTime);
+
+				//nFinalTime = EA::Thread::GetThreadTime();
+				//EATEST_VERIFY_MSG(nLockResult != RWMutexIP::kResultError, "RWMutexIP failure: read lock.");
+
+				if(nLockResult > 0)
+				{
+					nLocks++;
+					pWorkData->mnReadLockCount++;
+
+					EA::UnitTest::ReportVerbosity(2, "CValue = %d; EValue = %d\n", gSharedData->mnCalculatedValue, gSharedData->mnExpectedValue);
+					EATEST_VERIFY_MSG(gSharedData->mnCalculatedValue == gSharedData->mnExpectedValue, "RWMutexIP failure: read lock 2");
+				}
+			}
+
+			while(nLocks > 0)
+			{
+				// Verify no write locks are set.
+				nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeWrite);
+				EATEST_VERIFY_MSG(nLockResult == 0, "RWMutexIP failure: read lock verify 1.");
+
+				// Verify at least N read locks are set.
+				nLockResult = pWorkData->mRWMutexIP.GetLockCount(RWMutexIP::kLockTypeRead);
+				EATEST_VERIFY_MSG(nLockResult >= nLocks, "RWMutexIP failure: read lock verify 2.");
+
+				// Verify there is one less read lock set.
+				nLockResult = pWorkData->mRWMutexIP.Unlock();
+				EATEST_VERIFY_MSG(nLockResult >= nLocks-1, "RWMutexIP failure: read unlock.");
+
+				nLocks--;
+			}
+
+			EA::UnitTest::ThreadSleepRandom(10, 20);
+		}
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	return 0;
+}
+
+
+
+int TestThreadRWMutex()
+{
+	using namespace EA::Thread;
+
+	int nErrorCount(0);
+
+	EA::UnitTest::Report("Thread Pool Test\n");
+
+	/*
+	{ // ctor tests
+		// We test various combinations of RWMutexIP ctor and RWMutexIPParameters.
+		// RWMutexIPParameters(bool bIntraProcess = true, const char* pName = NULL);
+		// RWMutexIP(const RWMutexIPParameters* pRWMutexIPParameters = NULL, bool bDefaultParameters = true);
+
+	  //RWMutexIPParameters mp1(true,   NULL);
+	  //RWMutexIPParameters mp2(true,  "mp2");
+	  //RWMutexIPParameters mp3(false, "mp3");
+		RWMutexIPParameters mp4(false, "mp4");
+		RWMutexIPParameters mp6(false, "mp6");
+
+	  //RWMutexIP mutex1(&mp1, false);
+	  //RWMutexIP mutex2(&mp2, false);
+	  //RWMutexIP mutex3(&mp3, false);
+		RWMutexIP mutex4(&mp4, false);
+	  //RWMutexIP mutex5(NULL, true);
+		RWMutexIP mutex6(NULL, false);
+		mutex6.Init(&mp6);
+
+	  //AutoRWMutexIP am1(mutex1, RWMutexIP::kLockTypeRead);
+	  //AutoRWMutexIP am2(mutex2, RWMutexIP::kLockTypeRead);
+	  //AutoRWMutexIP am3(mutex3, RWMutexIP::kLockTypeRead);
+		AutoRWMutexIP am4(mutex4, RWMutexIP::kLockTypeRead);
+		AutoRWMutexIP am6(mutex6, RWMutexIP::kLockTypeRead);
+	}
+	*/
+
+	{
+		RWMWorkDataInterThread workData;
+		RWMutexIPParameters    rwMutexIPParameters(false, "RWMTest"); 
+
+		// Set up the RWMWorkData
+		workData.mRWMutexIP.Init(&rwMutexIPParameters);
+
+		// Create the threads
+		Thread*        pThreadArray   = new Thread[gTestThreadCount];
+		ThreadId*      pThreadIdArray = new ThreadId[gTestThreadCount];
+		Thread::Status status;
+
+		for(unsigned i(0); i < gTestThreadCount; i++)
+			pThreadIdArray[i] = pThreadArray[i].Begin(RWThreadFunction, &workData);
+
+		EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
+
+		workData.mbShouldQuit = true;
+
+		for(unsigned i(0); i < gTestThreadCount; i++)
+		{
+			if(pThreadIdArray[i] != kThreadIdInvalid)
+			{
+				status = pThreadArray[i].WaitForEnd(GetThreadTime() + 30000);
+
+				EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWMutexIP/Thread failure: status == kStatusRunning.");
+			}
+		}
+
+		delete[] pThreadIdArray;
+		delete[] pThreadArray;
+
+		nErrorCount += (int)workData.mnErrorCount;
+	}
+
+	return nErrorCount;
+}
+

+ 92 - 0
test/performance/source/PerfTestThread.cpp

@@ -0,0 +1,92 @@
+////////////////////////////////////////////////////////////////////////
+// PerfTestThread.cpp
+// 
+// Copyright (c) 2014, Electronic Arts Inc. All rights reserved.
+////////////////////////////////////////////////////////////////////////
+
+#include <benchmarkenvironment/test.h>
+#include <coreallocator/icoreallocator_interface.h>
+#include <EAMain/EAEntryPointMain.inl>
+#include <EAStdC/EAString.h>
+#include <EATest/EATest.h>
+#include <eathread/eathread.h>
+#include <MemoryMan/CoreAllocator.inl>
+#include <MemoryMan/MemoryMan.inl>
+
+#include "PerfTestThread.h"
+
+using namespace benchmarkenvironment;
+
+// TODO: Releases of benchmarkenvironmrnt with version numbers higher than 4.00
+//       will include a new macro that will replace most of this code. Presently, that macro
+//       cannot be used because it redefines operator new*.
+
+// The "BENCHMARKENVIRONMENT_TESTFUNCTION" macro redefines the new and new[] operators here, which causes a compiler error.
+// This code has been removed.
+EA_PREFIX_ALIGN(128) char gWorkMemory[BENCHMARKENVIRONMENT_WORKMEMORY_SIZE] EA_POSTFIX_ALIGN(128);
+EA_PREFIX_ALIGN(128) char gResultMemory[BENCHMARKENVIRONMENT_RESULTMEMORY_SIZE] EA_POSTFIX_ALIGN(128);
+
+int EAMain(int argc, char **argv)
+{
+	
+	Initialize(argc, argv, BENCHMARKENVIRONMENT_STRINGIZE(BENCHMARKENVIRONMENT_DEFAULT_TABLE_IDENTIFIER));
+	SetFlagsValid();
+	BenchmarkEnvironmentTestFunction(gWorkMemory, sizeof(gWorkMemory), gResultMemory, sizeof(gResultMemory));
+	Complete(BENCHMARKENVIRONMENT_RESULTPASSED);
+	return 0;
+}
+
+void BenchmarkEnvironmentTestFunction(Address /*workMemory*/, unsigned int /*workSize*/, Address resultMemory,	unsigned int resultSize)
+{
+
+	typedef void(*PerfTestFunction)(Results&, EA::IO::FileStream*);
+	typedef eastl::vector<PerfTestFunction> PerfTestFunctions;
+
+	PerfTestFunctions perfTestFunctions;
+	perfTestFunctions.push_back(&PerfTestThreadAtomic);
+	perfTestFunctions.push_back(&PerfTestThreadSemaphore);
+
+	// EATHREAD_PERFORMANCE_LOG_FILENAME is set in the build file. Right now it should be ${config}-performance_log.txt
+	EA::IO::FileStream performanceLog(EATHREAD_PERFORMANCE_LOG_FILENAME);
+
+	if (!gIsAutomatedDeferredRun)
+	{
+		// If this is a local run, create a performance log for the evaluation function to look at.
+		performanceLog.Open(EA::IO::kAccessFlagWrite, EA::IO::kCDCreateAlways);
+
+		// This loggin is mostly here so that we have a way to know that this code is NOT running
+		// in the build farm context. It could be removed once that has been confirmed.
+		EA::UnitTest::Report("Local Execution -> Performance Logging Enabled\n");
+	}
+
+	// Outline the structure of the results table
+	// We can do this out here because all of the tests will submit to the same table.
+	Results resultsTable(resultMemory, resultSize);
+	const int kNumColumns = 5;
+	resultsTable.DescribeTableBegin(kNumColumns);
+	resultsTable.AddStringField("Test Name");
+	resultsTable.AddDoubleField("Mean", "s");
+	resultsTable.AddDoubleField("Min", "s");
+	resultsTable.AddDoubleField("Max", "s");
+	resultsTable.AddDoubleField("Var", "s^2");
+	resultsTable.DescribeTableEnd();
+
+	for (unsigned int i = 0; i < perfTestFunctions.size(); ++i)
+	{
+		if (gIsAutomatedDeferredRun)
+			perfTestFunctions[i](resultsTable, NULL);
+		else
+			perfTestFunctions[i](resultsTable, &performanceLog);
+	}
+
+	if (!gIsAutomatedDeferredRun)
+	{
+		performanceLog.Close();
+
+		EA::UnitTest::Report("Log file written.\n");
+	}
+
+	return;
+}
+
+

+ 49 - 0
test/performance/source/PerfTestThread.h

@@ -0,0 +1,49 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+
+#ifndef PERFTESTTHREAD_H
+#define PERFTESTTHREAD_H
+
+#include <eathread/eathread_thread.h>
+#include <benchmarkenvironment/results.h>
+#include <benchmarkenvironment/statistics.h>
+#include <EAIO/EAFileStream.h>
+#include <EAStdC/EASprintf.h>
+
+typedef intptr_t(*ThreadEntryFunction)(void*);
+
+void PerfTestThreadAtomic(benchmarkenvironment::Results &results, EA::IO::FileStream* pLogFileStream);
+void PerfTestThreadSemaphore(benchmarkenvironment::Results &results, EA::IO::FileStream* pLogFileStream);
+
+inline void WriteToLogFile(EA::IO::FileStream* pLogFile, const char* formatString, ...)
+{
+	if(pLogFile)
+	{
+		const int kLogBufferSize = 256;
+		char8_t buffer[kLogBufferSize];
+
+		va_list arguments;
+		va_start(arguments, formatString);
+
+		int numCharsWritten = EA::StdC::Vsnprintf(buffer, kLogBufferSize, formatString, arguments);
+
+		va_end(arguments);
+
+		pLogFile->Write(buffer, numCharsWritten);
+	}
+}
+
+inline void AddRowToResults(benchmarkenvironment::Results& results, benchmarkenvironment::Sample& sample, eastl::string testName)
+{
+	results.Begin();
+	results.Add(testName.c_str());
+	results.Add(sample.GetMean());
+	results.Add(sample.GetMin());
+	results.Add(sample.GetMax());
+	results.Add(sample.GetVariance());
+	results.End();
+}
+
+#endif

+ 129 - 0
test/performance/source/PerfTestThreadAtomic.cpp

@@ -0,0 +1,129 @@
+////////////////////////////////////////////////////////////////////////
+// PerfTestThreadAtomic.cpp
+// 
+// Copyright (c) 2014, Electronic Arts Inc. All rights reserved.
+////////////////////////////////////////////////////////////////////////
+
+#include "benchmarkenvironment/results.h"
+#include "benchmarkenvironment/statistics.h"
+#include "benchmarkenvironment/timer.h"
+
+#include "eathread/eathread_atomic.h"
+#include "eathread/eathread_thread.h"
+#include "EATest/EATest.h"
+
+#include "PerfTestThread.h"
+
+using namespace EA::Thread;
+
+#if EA_THREADS_AVAILABLE
+
+// ------------------------------------------------------------------------
+// Each thread that is involved in these tests will get its own independent timer, and a 
+// shared atomic variable. With each thread doing its own timing, we should be able to 
+// remove scheduling noise to the greatest degree possible.
+//
+struct AtomicAndTimer 
+{
+	static AtomicInt32 mAtomicInteger;
+	benchmarkenvironment::Timer mLocalTimer;
+};
+
+AtomicInt32 AtomicAndTimer::mAtomicInteger = 0;
+
+// ------------------------------------------------------------------------
+//
+static intptr_t AtomicIntMath(void* pWorkStructure) 
+{
+	AtomicAndTimer& at = *static_cast<AtomicAndTimer*>(pWorkStructure);
+
+	// A series of atomic operations copied from the atomic unit test
+	at.mLocalTimer.Start();
+
+	++(at.mAtomicInteger);
+	--(at.mAtomicInteger);
+	(at.mAtomicInteger) += 5;
+	(at.mAtomicInteger) -= 5;
+	(at.mAtomicInteger)++;
+	(at.mAtomicInteger)--;
+
+	at.mLocalTimer.Stop();
+
+	return 0;
+}
+
+// --------------------------------------------------------------------------
+//
+// todo:  come up with a performance test for CAS operations.
+// static intptr_t AtomicIntCompareAndSwap(void* pArgs)
+// {
+//     AtomicAndTimer& at = static_cast<AtomicAndTimer&>(*pArgs);
+// 
+//     toCAS->mLocalTimer.Start();
+//     toCAS->mAtomicInteger.SetValueConditional(0, 1);
+//     toCAS->mLocalTimer.Stop();
+// 
+//     return 0;
+// }
+
+// ---------------------------------------------------------------------------
+//
+void AtomicIntPerfTest(ThreadEntryFunction ptestFunc, benchmarkenvironment::Sample &sample)
+{
+	static const int kNumThreads = 8;
+
+	EA::Thread::Thread::Status threadExitStatus;
+	AtomicAndTimer threadTimers[kNumThreads];
+	float totalTime = 0.0;
+
+	Thread threads[kNumThreads];
+
+	for (int i = 0; i < kNumThreads; ++i) 
+		threads[i].Begin(ptestFunc, &threadTimers[i]);
+
+	for (int i = 0; i < kNumThreads; ++i)
+	{
+		threadExitStatus = threads[i].WaitForEnd(GetThreadTime() + 30000);
+		
+		// Only take the results if the thread exited properly.
+		if (threadExitStatus != Thread::kStatusRunning)
+			totalTime += threadTimers[i].mLocalTimer.AsSeconds();
+	}
+
+	sample.AddElement(totalTime);
+}
+
+// ------------------------------------------------------------------------------
+// NOTE If you wish to add tests here. You will need to add your test function to the 
+//      testFunctions vector, as well as the name of the test to the testNames vector.
+void PerfTestThreadAtomic(benchmarkenvironment::Results &results, EA::IO::FileStream* pPerformanceLog)
+{
+	using namespace eastl;
+	using namespace benchmarkenvironment;
+
+	const int kNumSamples = 50;
+
+	vector<ThreadEntryFunction> testFunctions;
+	testFunctions.push_back(&AtomicIntMath);
+	// The compare-and-swap test has been deactivated until we can think of a better way to test that functionality.
+	//testFunctions.push_back(&AtomicIntCompareAndSwap));
+
+	vector<string> testNames;
+	testNames.push_back("Atomic Math Test");
+	//testNames.push_back("Atomic Compare and Swap Test");
+
+	vector<Sample> samples;
+	for (unsigned int i = 0; i < testFunctions.size(); ++i)
+		samples.push_back(Sample(kNumSamples));
+
+	for (unsigned int i = 0; i < testFunctions.size(); ++i)
+	{
+		for (int j = 0; j < kNumSamples; ++j)
+			AtomicIntPerfTest(testFunctions[i], samples[i]);
+
+		AddRowToResults(results, samples[i], testNames.at(i));
+		WriteToLogFile(pPerformanceLog, "%s,%g,%g\r\n", testNames[i].c_str(), samples[i].GetMean(), samples[i].GetVariance());
+	}
+}
+
+#endif // EA_THREADS_AVAILABLE

+ 392 - 0
test/performance/source/PerfTestThreadSemaphore.cpp

@@ -0,0 +1,392 @@
+////////////////////////////////////////////////////////////////////////
+// PerfTestThreadSemaphore.cpp
+//
+// Copyright (c) 2014, Electronic Arts Inc. All rights reserved.
+////////////////////////////////////////////////////////////////////////
+
+#include "benchmarkenvironment/results.h"
+#include "benchmarkenvironment/statistics.h"
+#include "benchmarkenvironment/timer.h"
+
+#include "eathread/eathread_atomic.h"
+#include "eathread/eathread_semaphore.h"
+#include "eathread/eathread_thread.h"
+
+#include "EATest/EATest.h"
+
+#include "PerfTestThread.h"
+
+using namespace EA::Thread;
+using namespace benchmarkenvironment;
+
+// Used to set how many times the contended thread functions run.
+const int kNumTestIterations = 10000;
+
+#define THREAD_WAIT_TIMEOUT 15000
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+//                           Producer/Consumer Tests & Test Functions
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+static AtomicInt32 gThreadSyncer = 0;
+void DECREMENT_AND_SPINWAIT(AtomicInt32& atomic_var) 
+{
+	atomic_var--;
+	while (atomic_var > 0) 
+		;
+}
+
+struct ProducerConsumerTestData
+{
+	static Semaphore* mpEmptySlots;
+	static Semaphore* mpFullSlots;
+	Timer mThreadLocalTimer;
+
+	ProducerConsumerTestData()
+		: mThreadLocalTimer() {}
+
+	static void InitSemaphores(int bufferCapacity)
+	{
+		SemaphoreParameters temp(bufferCapacity, true, "Producer/Consumer Full");
+		mpEmptySlots = new Semaphore(&temp);
+		
+		SemaphoreParameters temp2(0, false, "Producer/Consumer Empty");
+		mpFullSlots = new Semaphore(&temp2);
+	}
+
+	static void ResetSemaphores()
+	{
+		if (mpEmptySlots)
+		{
+			delete mpEmptySlots;
+			mpEmptySlots = NULL;
+		}
+		if (mpFullSlots)
+		{
+			delete mpFullSlots;
+			mpFullSlots = NULL;
+		}
+	}
+};
+
+// Initialization of the static members...
+Semaphore* ProducerConsumerTestData::mpEmptySlots = NULL;
+Semaphore* ProducerConsumerTestData::mpFullSlots = NULL;
+
+static intptr_t ProducerThreadFunction(void* pTestData)
+{
+	ProducerConsumerTestData& testData = *static_cast<ProducerConsumerTestData*>(pTestData);
+
+	testData.mThreadLocalTimer.Start();
+	for (int i = 0; i < kNumTestIterations; ++i)
+	{
+		testData.mpEmptySlots->Wait();
+		testData.mpFullSlots->Post();
+	}
+	testData.mThreadLocalTimer.Stop();
+
+	EAT_ASSERT(testData.mThreadLocalTimer.AsSeconds() >= 0.0);
+
+	return 0;
+}
+
+static intptr_t ConsumerThreadFunction(void* pTestData)
+{
+	ProducerConsumerTestData& testData = *static_cast<ProducerConsumerTestData*>(pTestData);
+
+	testData.mThreadLocalTimer.Start();
+	for (int i = 0; i < kNumTestIterations; ++i)
+	{
+		testData.mpFullSlots->Wait();
+		testData.mpEmptySlots->Post();
+	}
+	testData.mThreadLocalTimer.Stop();
+
+	EAT_ASSERT(testData.mThreadLocalTimer.AsSeconds() >= 0.0);
+
+	return 0;
+}
+
+void ProducerConsumerTest(
+	Sample& sample,
+	ThreadEntryFunction pProducer,
+	ThreadEntryFunction pConsumer,
+	int bufferCapacity,
+	bool isContended) 
+{
+	const int kThreadArraySize = 12;
+	const int kMinThreads = 4;
+
+	const int kNumCores = (isContended ? eastl::min(kThreadArraySize, eastl::max(GetProcessorCount(), kMinThreads)) : 2);
+	const int kThreadGroupSize = (isContended ? kNumCores / 2 : 1);
+
+	ProducerConsumerTestData::InitSemaphores(bufferCapacity);
+
+	eastl::vector<ProducerConsumerTestData> producerThreadTimers(kThreadGroupSize);
+	eastl::vector<ProducerConsumerTestData> consumerThreadTimers(kThreadGroupSize);
+	eastl::vector<Thread> producers(kThreadGroupSize);
+	eastl::vector<Thread> consumers(kThreadGroupSize);
+
+	ThreadAffinityMask affinityMask = 1;
+	ThreadId newThread;
+	for (int i = 0; i < kThreadGroupSize; ++i)
+	{
+		newThread = producers[i].Begin(pProducer, &producerThreadTimers[i]);
+		EA::Thread::SetThreadAffinityMask(newThread, affinityMask);
+		affinityMask = affinityMask << 1;
+
+		newThread = consumers[i].Begin(pConsumer, &consumerThreadTimers[i]);
+		EA::Thread::SetThreadAffinityMask(newThread, affinityMask);
+		affinityMask = affinityMask << 1;
+	}
+
+	for (int i = 0; i < kThreadGroupSize; ++i)
+	{
+		EA::Thread::Thread::Status producerThreadExitStatus = producers[i].WaitForEnd(GetThreadTime() + (THREAD_WAIT_TIMEOUT * kNumCores));
+		EA::Thread::Thread::Status consumerThreadExitStatus = consumers[i].WaitForEnd(GetThreadTime() + (THREAD_WAIT_TIMEOUT * kNumCores));
+
+		EA_UNUSED(producerThreadExitStatus);
+		EA_UNUSED(consumerThreadExitStatus);
+
+		EAT_ASSERT(producerThreadExitStatus != Thread::kStatusRunning);
+		EAT_ASSERT(consumerThreadExitStatus != Thread::kStatusRunning);
+	}
+
+	double totalTime = 0.0;
+	for (int i = 0; i < kThreadGroupSize; ++i)
+	{
+		totalTime += producerThreadTimers[i].mThreadLocalTimer.AsSeconds();
+		totalTime += consumerThreadTimers[i].mThreadLocalTimer.AsSeconds();
+	}
+
+	sample.AddElement(totalTime);
+
+	ProducerConsumerTestData::ResetSemaphores();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+//                           Scheduler Tests & Test Functions
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+struct SemaphoreTestData
+{
+	static Semaphore* mpTestSemaphore;
+	Timer& mThreadLocalTimer;
+	AtomicInt32 mSignal;
+
+	SemaphoreTestData(Timer &timer, int signal = 0)
+		: mThreadLocalTimer(timer)
+		, mSignal(signal)
+	{
+	}
+
+	SemaphoreTestData operator=(const SemaphoreTestData& other)
+	{
+		mThreadLocalTimer = other.mThreadLocalTimer;
+		mSignal = other.mSignal;
+
+		return *this;
+	}
+	
+	SemaphoreTestData(const SemaphoreTestData& other) 
+		: mThreadLocalTimer(other.mThreadLocalTimer)
+		, mSignal(other.mSignal) 
+	{
+	}
+	
+	static void setSemaphoreInitialCount(int count)
+	{
+		SemaphoreParameters params(count, true, "Test Semaphore");
+		mpTestSemaphore = new Semaphore(&params);
+	}
+
+	static void resetSemaphore()
+	{
+		if (mpTestSemaphore)
+		{
+			delete mpTestSemaphore;
+			mpTestSemaphore = NULL;
+		}
+	}
+};
+
+Semaphore* SemaphoreTestData::mpTestSemaphore = NULL;
+
+// ------------------------------------------------------------------------------
+//
+
+static intptr_t SemaphoreTestSchedulerContendedFunction(void* pTestData)
+{
+	// In this case, each thread will have its own timer, and share the same semaphore.
+	SemaphoreTestData& testData = *static_cast<SemaphoreTestData*>(pTestData);
+
+	DECREMENT_AND_SPINWAIT(gThreadSyncer);
+	testData.mThreadLocalTimer.Start();
+
+	for (int i = 0; i < kNumTestIterations; ++i)
+	{
+		testData.mpTestSemaphore->Wait();
+		testData.mpTestSemaphore->Post();
+	}
+
+	testData.mThreadLocalTimer.Stop();
+
+	return 0;
+}
+
+// ------------------------------------------------------------------------------
+//
+static intptr_t SemaphoreTestSchedulerUncontendedWakeFunction(void* pTestData)
+{
+	// Initiate the timer when the wakeup signal is sent.
+	SemaphoreTestData& testData = *static_cast<SemaphoreTestData*>(pTestData);
+
+	testData.mThreadLocalTimer.Start();
+	testData.mpTestSemaphore->Post();
+
+	return 0;
+}
+
+// ------------------------------------------------------------------------------
+//
+static intptr_t SemaphoreTestSchedulerUncontendedWaitFunction(void* pTestData)
+{
+	// Immediately go to sleep waiting for the semaphore, then stop the timer 
+	// once we wake up.
+	SemaphoreTestData& testData = *static_cast<SemaphoreTestData*>(pTestData);
+
+	testData.mSignal++;
+	int exitResult = testData.mpTestSemaphore->Wait(GetThreadTime() + THREAD_WAIT_TIMEOUT);
+	EAT_ASSERT(exitResult != Semaphore::kResultTimeout);
+	EA_UNUSED(exitResult); // Silences gcc and clang warnings 
+
+	testData.mThreadLocalTimer.Stop();
+
+	return 0;
+}
+
+// ------------------------------------------------------------------------------
+// 
+void SemaphoreUncontendedPerfTest(Sample &sample, ThreadEntryFunction pWaitingFunc, ThreadEntryFunction pWakingFunc, int semaphoreInitialCount)
+{
+	SemaphoreParameters params(semaphoreInitialCount, true, "Uncontended");
+	Timer timer;
+
+	SemaphoreTestData sharedData(timer);
+	SemaphoreTestData::setSemaphoreInitialCount(semaphoreInitialCount);
+
+	Thread waker;
+	Thread sleeper;
+
+	sleeper.Begin(pWaitingFunc, &sharedData);
+
+	// Spin until the sleeping thread runs and blocks on the semaphore.
+	while (sharedData.mSignal == 0) {}
+
+	waker.Begin(pWakingFunc, &sharedData);
+
+	EA::Thread::Thread::Status waiterThreadExitStatus = sleeper.WaitForEnd(GetThreadTime() + THREAD_WAIT_TIMEOUT);
+	EA::Thread::Thread::Status wakerThreadExitStatus = waker.WaitForEnd(GetThreadTime() + THREAD_WAIT_TIMEOUT);
+	
+	EA_UNUSED(waiterThreadExitStatus);
+	EA_UNUSED(wakerThreadExitStatus);
+
+	EAT_ASSERT(waiterThreadExitStatus != Thread::kStatusRunning && wakerThreadExitStatus != Thread::kStatusRunning);
+
+	sample.AddElement(timer.AsSeconds());
+
+	SemaphoreTestData::resetSemaphore();
+}
+
+// ------------------------------------------------------------------------------
+// 
+void SemaphoreContendedPerfTest(benchmarkenvironment::Sample &sample, ThreadEntryFunction pTestFunc, int semaphoreInitialCount)
+{
+	// The contended test will always use per-thread timers, since any blocks are
+	// a circumstance that would arise in normal use.
+
+	const int kThreadArraySize = 12;
+	const int kMinThreads = 4;
+
+	const int kNumCores = eastl::min(kThreadArraySize, eastl::max(GetProcessorCount(), kMinThreads));
+	gThreadSyncer = kNumCores;
+
+	SemaphoreTestData::setSemaphoreInitialCount(semaphoreInitialCount);
+
+	eastl::vector<Thread> threads(kNumCores);
+	eastl::vector<Timer> timers(kNumCores);
+	eastl::vector<SemaphoreTestData> data;
+	for (int i = 0; i < kNumCores; ++i)
+		data.push_back(SemaphoreTestData(timers[i]));
+
+	for (int i = 0; i < kNumCores; ++i)
+	{
+		ThreadId newThread = threads[i].Begin(pTestFunc, &data[i]);
+		EA::Thread::SetThreadAffinityMask(newThread, ThreadAffinityMask(1 << i));
+	}
+
+	double totalTime = 0.0;
+	for (int i = 0; i < kNumCores; ++i)
+	{
+		EA::Thread::Thread::Status threadExitStatus = threads[i].WaitForEnd(GetThreadTime() + (THREAD_WAIT_TIMEOUT * kNumCores));
+		EAT_ASSERT(threadExitStatus != Thread::kStatusRunning);
+		EA_UNUSED(threadExitStatus);
+			
+		totalTime += timers[i].AsSeconds();
+	}
+
+	sample.AddElement(totalTime);
+
+	SemaphoreTestData::resetSemaphore();
+}
+
+// ------------------------------------------------------------------------------
+//
+void PerfTestThreadSemaphore(Results &results, EA::IO::FileStream* pPerformanceLog)
+{
+	using namespace eastl;
+
+	const int kNumSamples = 10;
+	const int kNumTests = 7;
+	
+	vector<Sample> samples;
+	for (int i = 0; i < kNumTests; ++i)
+		samples.push_back(Sample(kNumSamples));
+
+	for (int j = 0; j < kNumSamples; ++j)
+		SemaphoreUncontendedPerfTest(samples[0], &SemaphoreTestSchedulerUncontendedWaitFunction, &SemaphoreTestSchedulerUncontendedWakeFunction, 0);
+	AddRowToResults(results, samples[0], "Semaphore Wakeup Time");
+	WriteToLogFile(pPerformanceLog, "Semaphore Wakeup Time,%g,%g\r\n", samples[0].GetMean(), samples[0].GetVariance());  // Execution is in a local context, so output the results to the log file.
+
+	for (int j = 0; j < kNumSamples; ++j)
+		SemaphoreContendedPerfTest(samples[1], &SemaphoreTestSchedulerContendedFunction, 1);
+	AddRowToResults(results, samples[1], "Semaphore as Mutex");
+	WriteToLogFile(pPerformanceLog, "Semaphore as Mutex,%g,%g\r\n", samples[1].GetMean(), samples[1].GetVariance());
+
+	for (int j = 0; j < kNumSamples; ++j)
+		SemaphoreContendedPerfTest(samples[2], &SemaphoreTestSchedulerContendedFunction, 5);
+	AddRowToResults(results, samples[2], "Semaphore as 5-way Mutex");
+	WriteToLogFile(pPerformanceLog, "Semaphore as 5-way Mutex,%g,%g\r\n", samples[2].GetMean(), samples[2].GetVariance());
+
+	for (int j = 0; j < kNumSamples; ++j)
+		ProducerConsumerTest(samples[3], &ProducerThreadFunction, &ConsumerThreadFunction, 1, false);
+	AddRowToResults(results, samples[3], "1 P/1 C (1 Thread at once)");
+	WriteToLogFile(pPerformanceLog, "1 P/1 C (1 Thread at once),%g,%g\r\n", samples[3].GetMean(), samples[3].GetVariance());
+
+	for (int j = 0; j < kNumSamples; ++j)
+		ProducerConsumerTest(samples[4], &ProducerThreadFunction, &ConsumerThreadFunction, 5, false);
+	AddRowToResults(results, samples[4], "1 P/1 C (5 Threads at once)");
+	WriteToLogFile(pPerformanceLog, "1 P/1 C (5 Threads at once),%g,%g\r\n", samples[4].GetMean(), samples[4].GetVariance());
+
+	for (int j = 0; j < kNumSamples; ++j)
+		ProducerConsumerTest(samples[5], &ProducerThreadFunction, &ConsumerThreadFunction, 1, true);
+	AddRowToResults(results, samples[5], "1+ P/1+ C (1 Thread at once)");
+	WriteToLogFile(pPerformanceLog, "1+ P/1+ C (1 Thread at once),%g,%g\r\n", samples[5].GetMean(), samples[5].GetVariance());
+
+	for (int j = 0; j < kNumSamples; ++j)
+		ProducerConsumerTest(samples[6], &ProducerThreadFunction, &ConsumerThreadFunction, 5, true);
+	AddRowToResults(results, samples[6], "1+ P/1+ C (5 Threads at once)");
+	WriteToLogFile(pPerformanceLog, "1+ P/1+ C (5 Threads at once),%g,%g\r\n", samples[6].GetMean(), samples[6].GetVariance());
+
+	return;
+}

+ 1313 - 0
test/thread/lib/pthreads-win32/include/pthread.h

@@ -0,0 +1,1313 @@
+/* This is an implementation of the threads API of POSIX 1003.1-2001.
+ *
+ * --------------------------------------------------------------------------
+ *
+ *      Pthreads-win32 - POSIX Threads Library for Win32
+ *      Copyright(C) 1998 John E. Bossom
+ *      Copyright(C) 1999,2003 Pthreads-win32 contributors
+ * 
+ *      Contact Email: [email protected]
+ * 
+ *      The current list of contributors is contained
+ *      in the file CONTRIBUTORS included with the source
+ *      code distribution. The list can also be seen at the
+ *      following World Wide Web location:
+ *      http://sources.redhat.com/pthreads-win32/contributors.html
+ * 
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation; either
+ *      version 2 of the License, or (at your option) any later version.
+ * 
+ *      This library is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *      Lesser General Public License for more details.
+ * 
+ *      You should have received a copy of the GNU Lesser General Public
+ *      License along with this library in the file COPYING.LIB;
+ *      if not, write to the Free Software Foundation, Inc.,
+ *      59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#if !defined( PTHREAD_H )
+#define PTHREAD_H
+
+#undef PTW32_LEVEL
+
+#if defined(_POSIX_SOURCE)
+#define PTW32_LEVEL 0
+/* Early POSIX */
+#endif
+
+#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309
+#undef PTW32_LEVEL
+#define PTW32_LEVEL 1
+/* Include 1b, 1c and 1d */
+#endif
+
+#if defined(INCLUDE_NP)
+#undef PTW32_LEVEL
+#define PTW32_LEVEL 2
+/* Include Non-Portable extensions */
+#endif
+
+#define PTW32_LEVEL_MAX 3
+
+#if !defined(PTW32_LEVEL)
+#define PTW32_LEVEL PTW32_LEVEL_MAX
+/* Include everything */
+#endif
+
+#ifdef _UWIN
+#   define HAVE_STRUCT_TIMESPEC 1
+#   define HAVE_SIGNAL_H	1
+#   undef HAVE_CONFIG_H
+#   pragma comment(lib, "pthread")
+#endif
+
+/*
+ * -------------------------------------------------------------
+ *
+ *
+ * Module: pthread.h
+ *
+ * Purpose:
+ *	Provides an implementation of PThreads based upon the
+ *	standard:
+ *
+ *		POSIX 1003.1-2001
+ *  and
+ *    The Single Unix Specification version 3
+ *
+ *    (these two are equivalent)
+ *
+ *	in order to enhance code portability between Windows,
+ *  various commercial Unix implementations, and Linux.
+ *
+ *	See the ANNOUNCE file for a full list of conforming
+ *	routines and defined constants, and a list of missing
+ *	routines and constants not defined in this implementation.
+ *
+ * Authors:
+ *	There have been many contributors to this library.
+ *	The initial implementation was contributed by
+ *	John Bossom, and several others have provided major
+ *	sections or revisions of parts of the implementation.
+ *	Often significant effort has been contributed to
+ *	find and fix important bugs and other problems to
+ *	improve the reliability of the library, which sometimes
+ *	is not reflected in the amount of code which changed as
+ *	result.
+ *	As much as possible, the contributors are acknowledged
+ *	in the ChangeLog file in the source code distribution
+ *	where their changes are noted in detail.
+ *
+ *	Contributors are listed in the CONTRIBUTORS file.
+ *
+ *	As usual, all bouquets go to the contributors, and all
+ *	brickbats go to the project maintainer.
+ *
+ * Maintainer:
+ *	The code base for this project is coordinated and
+ *	eventually pre-tested, packaged, and made available by
+ *
+ *		Ross Johnson <[email protected]>
+ *
+ * QA Testers:
+ *	Ultimately, the library is tested in the real world by
+ *	a host of competent and demanding scientists and
+ *	engineers who report bugs and/or provide solutions
+ *	which are then fixed or incorporated into subsequent
+ *	versions of the library. Each time a bug is fixed, a
+ *	test case is written to prove the fix and ensure
+ *	that later changes to the code don't reintroduce the
+ *	same error. The number of test cases is slowly growing
+ *	and therefore so is the code reliability.
+ *
+ * Compliance:
+ *	See the file ANNOUNCE for the list of implemented
+ *	and not-implemented routines and defined options.
+ *	Of course, these are all defined is this file as well.
+ *
+ * Web site:
+ *	The source code and other information about this library
+ *	are available from
+ *
+ *		http://sources.redhat.com/pthreads-win32/
+ *
+ * -------------------------------------------------------------
+ */
+
+/* Try to avoid including windows.h */
+#if defined(__MINGW32__) && defined(__cplusplus)
+/*
+ * FIXME: The pthreadGCE.dll build gets linker unresolved errors
+ * on pthread_key_create() unless windows.h is included here.
+ * It appears to have something to do with an argument type mismatch.
+ * Looking at tsd.o with 'nm' shows this line:
+ * 00000000 T _pthread_key_create__FPP14pthread_key_t_PFPv_v
+ * instead of
+ * 00000000 T _pthread_key_create
+ */
+#define PTW32_INCLUDE_WINDOWS_H
+#endif
+
+#ifdef PTW32_INCLUDE_WINDOWS_H
+#ifndef WIN32_LEAN_AND_MEAN
+	#define WIN32_LEAN_AND_MEAN
+#endif
+#include <Windows.h>
+#endif
+
+/*
+ * -----------------
+ * autoconf switches
+ * -----------------
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#if PTW32_LEVEL >= PTW32_LEVEL_MAX
+
+/* Try to avoid including windows.h */
+#if defined(__MINGW32__) && defined(__cplusplus)
+/*
+ * FIXME: The pthreadGCE.dll build gets linker unresolved errors
+ * on pthread_key_create() unless windows.h is included here.
+ * It appears to have something to do with an argument type mismatch.
+ * Looking at tsd.o with 'nm' shows this line:
+ * 00000000 T _pthread_key_create__FPP14pthread_key_t_PFPv_v
+ * instead of
+ * 00000000 T _pthread_key_create
+ */
+#define PTW32_INCLUDE_WINDOWS_H
+#endif
+
+#ifdef PTW32_INCLUDE_WINDOWS_H
+#ifndef WIN32_LEAN_AND_MEAN
+	#define WIN32_LEAN_AND_MEAN
+#endif
+#include <Windows.h>
+#endif
+
+#ifndef NEED_FTIME
+#include <time.h>
+#else /* NEED_FTIME */
+/* use native WIN32 time API */
+#endif /* NEED_FTIME */
+
+#if HAVE_SIGNAL_H
+#include <signal.h>
+#endif /* HAVE_SIGNAL_H */
+
+#include <setjmp.h>
+#include <limits.h>
+
+/*
+ * Boolean values to make us independent of system includes.
+ */
+enum {
+  PTW32_FALSE = 0,
+  PTW32_TRUE = (! PTW32_FALSE)
+};
+
+/*
+ * This is a duplicate of what is in the autoconf config.h,
+ * which is only used when building the pthread-win32 libraries.
+ */
+
+#ifndef PTW32_CONFIG_H
+#  if defined(WINCE)
+#    define NEED_ERRNO
+#    define NEED_SEM
+#  endif
+#  if defined(_UWIN) || defined(__MINGW32__)
+#    define HAVE_MODE_T
+#  endif
+#endif
+
+/*
+ *
+ */
+
+#if PTW32_LEVEL >= PTW32_LEVEL_MAX
+#ifdef NEED_ERRNO
+#include "need_errno.h"
+#else
+#include <errno.h>
+#endif
+#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */
+
+/*
+ * Several systems don't define ENOTSUP. If not, we use
+ * the same value as Solaris.
+ */
+#ifndef ENOTSUP
+#  define ENOTSUP 48
+#endif
+
+#ifndef ETIMEDOUT
+#  define ETIMEDOUT 10060     /* This is the value in winsock.h. */
+#endif
+
+#include <sched.h>
+
+/*
+ * To avoid including windows.h we define only those things that we
+ * actually need from it. I don't like the potential incompatibility that
+ * this creates with future versions of windows.
+ */
+#ifndef PTW32_INCLUDE_WINDOWS_H
+#ifndef HANDLE
+# define PTW32__HANDLE_DEF
+# define HANDLE void *
+#endif
+#ifndef DWORD
+# define PTW32__DWORD_DEF
+# define DWORD unsigned long
+#endif
+#endif
+
+#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */
+
+#ifndef HAVE_STRUCT_TIMESPEC
+struct timespec {
+	long tv_sec;
+	long tv_nsec;
+};
+#endif /* HAVE_STRUCT_TIMESPEC */
+
+#ifndef SIG_BLOCK
+#define SIG_BLOCK 0
+#endif /* SIG_BLOCK */
+
+#ifndef SIG_UNBLOCK 
+#define SIG_UNBLOCK 1
+#endif /* SIG_UNBLOCK */
+
+#ifndef SIG_SETMASK
+#define SIG_SETMASK 2
+#endif /* SIG_SETMASK */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif				/* __cplusplus */
+
+/*
+ * -------------------------------------------------------------
+ *
+ * POSIX 1003.1-2001 Options
+ * =========================
+ *
+ * _POSIX_THREADS (set)
+ *			If set, you can use threads
+ *
+ * _POSIX_THREAD_ATTR_STACKSIZE (set)
+ *			If set, you can control the size of a thread's
+ *			stack
+ *				pthread_attr_getstacksize
+ *				pthread_attr_setstacksize
+ *
+ * _POSIX_THREAD_ATTR_STACKADDR (not set)
+ *			If set, you can allocate and control a thread's
+ *			stack. If not supported, the following functions
+ *			will return ENOSYS, indicating they are not
+ *			supported:
+ *				pthread_attr_getstackaddr
+ *				pthread_attr_setstackaddr
+ *
+ * _POSIX_THREAD_PRIORITY_SCHEDULING (set)
+ *			If set, you can use realtime scheduling.
+ *			Indicates the availability of:
+ *				pthread_attr_getinheritsched
+ *				pthread_attr_getschedparam
+ *				pthread_attr_getschedpolicy
+ *				pthread_attr_getscope
+ *				pthread_attr_setinheritsched
+ *				pthread_attr_setschedparam
+ *				pthread_attr_setschedpolicy
+ *				pthread_attr_setscope
+ *				pthread_getschedparam
+ *				pthread_setschedparam
+ *				sched_get_priority_max
+ *				sched_get_priority_min
+ *				sched_rr_set_interval
+ *
+ * _POSIX_THREAD_PRIO_INHERIT (not set)
+ *			If set, you can create priority inheritance
+ *			mutexes.
+ *				pthread_mutexattr_getprotocol +
+ *				pthread_mutexattr_setprotocol +
+ *
+ * _POSIX_THREAD_PRIO_PROTECT (not set)
+ *			If set, you can create priority ceiling mutexes
+ *			Indicates the availability of:
+ *				pthread_mutex_getprioceiling
+ *				pthread_mutex_setprioceiling
+ *				pthread_mutexattr_getprioceiling
+ *				pthread_mutexattr_getprotocol	  +
+ *				pthread_mutexattr_setprioceiling
+ *				pthread_mutexattr_setprotocol	  +
+ *
+ * _POSIX_THREAD_PROCESS_SHARED (not set)
+ *			If set, you can create mutexes and condition
+ *			variables that can be shared with another
+ *			process.If set, indicates the availability
+ *			of:
+ *				pthread_mutexattr_getpshared
+ *				pthread_mutexattr_setpshared
+ *				pthread_condattr_getpshared
+ *				pthread_condattr_setpshared
+ *
+ * _POSIX_THREAD_SAFE_FUNCTIONS (set)
+ *			If set you can use the special *_r library
+ *			functions that provide thread-safe behaviour
+ *
+ * _POSIX_READER_WRITER_LOCKS (set)
+ *			If set, you can use read/write locks
+ *
+ * _POSIX_SPIN_LOCKS (set)
+ *			If set, you can use spin locks
+ *
+ * _POSIX_BARRIERS (set)
+ *			If set, you can use barriers
+ *
+ *	+ These functions provide both 'inherit' and/or
+ *	  'protect' protocol, based upon these macro
+ *	  settings.
+ *
+ * POSIX 1003.1-2001 Limits
+ * ===========================
+ *
+ * PTHREAD_DESTRUCTOR_ITERATIONS
+ *			Maximum number of attempts to destroy
+ *			a thread's thread-specific data on
+ *			termination (must be at least 4)
+ *
+ * PTHREAD_KEYS_MAX
+ *			Maximum number of thread-specific data keys
+ *			available per process (must be at least 128)
+ *
+ * PTHREAD_STACK_MIN
+ *			Minimum supported stack size for a thread
+ *
+ * PTHREAD_THREADS_MAX
+ *			Maximum number of threads supported per
+ *			process (must be at least 64).
+ *
+ * _POSIX_SEM_NSEMS_MAX
+ *	The maximum number of semaphores a process can have.
+ *	(only defined if not already defined)
+ *
+ * _POSIX_SEM_VALUE_MAX
+ *	The maximum value a semaphore can have.
+ *	(only defined if not already defined)
+ *
+ * -------------------------------------------------------------
+ */
+
+/*
+ * POSIX Options
+ */
+#ifndef _POSIX_THREADS
+#define _POSIX_THREADS
+#endif
+
+#ifndef _POSIX_READER_WRITER_LOCKS
+#define _POSIX_READER_WRITER_LOCKS
+#endif
+
+#ifndef _POSIX_SPIN_LOCKS
+#define _POSIX_SPIN_LOCKS
+#endif
+
+#ifndef _POSIX_BARRIERS
+#define _POSIX_BARRIERS
+#endif
+
+#define _POSIX_THREAD_SAFE_FUNCTIONS
+#define _POSIX_THREAD_ATTR_STACKSIZE
+#define _POSIX_THREAD_PRIORITY_SCHEDULING
+
+#if defined( KLUDGE )
+/*
+ * The following are not supported
+ */
+#define _POSIX_THREAD_ATTR_STACKADDR
+#define _POSIX_THREAD_PRIO_INHERIT
+#define _POSIX_THREAD_PRIO_PROTECT
+#define _POSIX_THREAD_PROCESS_SHARED
+
+#endif				/* KLUDGE */
+
+/*
+ * POSIX Limits
+ *
+ *	PTHREAD_DESTRUCTOR_ITERATIONS
+ *		Standard states this must be at least
+ *		4.
+ *
+ *	PTHREAD_KEYS_MAX
+ *		WIN32 permits only 64 TLS keys per process.
+ *		This limitation could be worked around by
+ *		simply simulating keys.
+ *
+ *	PTHREADS_STACK_MIN
+ *		POSIX specifies 0 which is also the value WIN32
+ *		interprets as allowing the system to
+ *		set the size to that of the main thread. The
+ *		maximum stack size in Win32 is 1Meg. WIN32
+ *		allocates more stack as required up to the 1Meg
+ *		limit.
+ *
+ *	PTHREAD_THREADS_MAX
+ *		Not documented by WIN32. Wrote a test program
+ *		that kept creating threads until it failed
+ *		revealed this approximate number (Windows NT).
+ *		This number is somewhat less for Windows 9x
+ *		and is effectively less than 64. Perhaps this
+ *		constant should be set at DLL load time.
+ *
+ */
+#define PTHREAD_DESTRUCTOR_ITERATIONS			       4
+#define PTHREAD_KEYS_MAX			64
+#define PTHREAD_STACK_MIN			 0
+#define PTHREAD_THREADS_MAX		      2019
+#ifndef _POSIX_SEM_NSEMS_MAX
+/* Not used and only an arbitrary value. */
+#  define _POSIX_SEM_NSEMS_MAX		      1024
+#endif
+#ifndef _POSIX_SEM_VALUE_MAX
+#  define _POSIX_SEM_VALUE_MAX	       (INT_MAX/2)
+#endif
+
+#if __GNUC__ && ! defined (__declspec)
+# error Please upgrade your GNU compiler to one that supports __declspec.
+#endif
+
+/*
+ * When building the DLL code, you should define PTW32_BUILD so that
+ * the variables/functions are exported correctly. When using the DLL,
+ * do NOT define PTW32_BUILD, and then the variables/functions will
+ * be imported correctly.
+ */
+#ifdef _DLL
+#  ifdef PTW32_BUILD
+#    define PTW32_DLLPORT __declspec (dllexport)
+#  else
+#    define PTW32_DLLPORT __declspec (dllimport)
+#  endif
+#endif
+
+#if defined(_UWIN) && PTW32_LEVEL >= PTW32_LEVEL_MAX
+#   include	<sys/types.h>
+#else
+typedef struct pthread_t_ *pthread_t;
+typedef struct pthread_attr_t_ *pthread_attr_t;
+typedef struct pthread_once_t_ pthread_once_t;
+typedef struct pthread_key_t_ *pthread_key_t;
+typedef struct pthread_mutex_t_ *pthread_mutex_t;
+typedef struct pthread_mutexattr_t_ *pthread_mutexattr_t;
+typedef struct pthread_cond_t_ *pthread_cond_t;
+typedef struct pthread_condattr_t_ *pthread_condattr_t;
+#endif
+typedef struct pthread_rwlock_t_ *pthread_rwlock_t;
+typedef struct pthread_rwlockattr_t_ *pthread_rwlockattr_t;
+typedef struct pthread_spinlock_t_ *pthread_spinlock_t;
+typedef struct pthread_barrier_t_ *pthread_barrier_t;
+typedef struct pthread_barrierattr_t_ *pthread_barrierattr_t;
+
+/*
+ * ====================
+ * ====================
+ * POSIX Threads
+ * ====================
+ * ====================
+ */
+
+enum {
+/*
+ * pthread_attr_{get,set}detachstate
+ */
+  PTHREAD_CREATE_JOINABLE	= 0,  /* Default */
+  PTHREAD_CREATE_DETACHED	= 1,
+
+/*
+ * pthread_attr_{get,set}inheritsched
+ */
+  PTHREAD_INHERIT_SCHED 	= 0,
+  PTHREAD_EXPLICIT_SCHED	= 1,  /* Default */
+
+/*
+ * pthread_{get,set}scope
+ */
+  PTHREAD_SCOPE_PROCESS 	= 0,
+  PTHREAD_SCOPE_SYSTEM		= 1,  /* Default */
+
+/*
+ * pthread_setcancelstate paramters
+ */
+  PTHREAD_CANCEL_ENABLE 	= 0,  /* Default */
+  PTHREAD_CANCEL_DISABLE	= 1,
+
+/*
+ * pthread_setcanceltype parameters
+ */
+  PTHREAD_CANCEL_ASYNCHRONOUS	= 0,
+  PTHREAD_CANCEL_DEFERRED	= 1,  /* Default */
+
+/*
+ * pthread_mutexattr_{get,set}pshared
+ * pthread_condattr_{get,set}pshared
+ */
+  PTHREAD_PROCESS_PRIVATE	= 0,
+  PTHREAD_PROCESS_SHARED	= 1,
+
+/*
+ * pthread_barrier_wait
+ */
+  PTHREAD_BARRIER_SERIAL_THREAD = -1
+};
+
+/*
+ * ====================
+ * ====================
+ * Cancelation
+ * ====================
+ * ====================
+ */
+#define PTHREAD_CANCELED       ((void *) -1)
+
+
+/*
+ * ====================
+ * ====================
+ * Once Key
+ * ====================
+ * ====================
+ */
+#define PTHREAD_ONCE_INIT	{ PTW32_FALSE, -1 }
+
+struct pthread_once_t_
+{
+  int done;		    /* indicates if user function executed  */
+  long started; 	    /* First thread to increment this value */
+				/* to zero executes the user function   */
+};
+
+
+/*
+ * ====================
+ * ====================
+ * Object initialisers
+ * ====================
+ * ====================
+ */
+#define PTHREAD_MUTEX_INITIALIZER ((pthread_mutex_t) -1)
+
+#define PTHREAD_COND_INITIALIZER ((pthread_cond_t) -1)
+
+#define PTHREAD_RWLOCK_INITIALIZER ((pthread_rwlock_t) -1)
+
+#define PTHREAD_SPINLOCK_INITIALIZER ((pthread_spinlock_t) -1)
+
+
+/*
+ * Mutex types.
+ */
+enum
+{
+  /* Compatibility with LinuxThreads */
+  PTHREAD_MUTEX_FAST_NP,
+  PTHREAD_MUTEX_RECURSIVE_NP,
+  PTHREAD_MUTEX_ERRORCHECK_NP,
+  PTHREAD_MUTEX_TIMED_NP = PTHREAD_MUTEX_FAST_NP,
+  PTHREAD_MUTEX_ADAPTIVE_NP = PTHREAD_MUTEX_FAST_NP,
+  /* For compatibility with POSIX */
+  PTHREAD_MUTEX_NORMAL = PTHREAD_MUTEX_FAST_NP,
+  PTHREAD_MUTEX_RECURSIVE = PTHREAD_MUTEX_RECURSIVE_NP,
+  PTHREAD_MUTEX_ERRORCHECK = PTHREAD_MUTEX_ERRORCHECK_NP,
+  PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL
+};
+
+
+/* There are three implementations of cancel cleanup.
+ * Note that pthread.h is included in both application
+ * compilation units and also internally for the library.
+ * The code here and within the library aims to work
+ * for all reasonable combinations of environments.
+ *
+ * The three implementations are:
+ *
+ *   WIN32 SEH
+ *   C
+ *   C++
+ *
+ * Please note that exiting a push/pop block via
+ * "return", "exit", "break", or "continue" will
+ * lead to different behaviour amongst applications
+ * depending upon whether the library was built
+ * using SEH, C++, or C. For example, a library built
+ * with SEH will call the cleanup routine, while both
+ * C++ and C built versions will not.
+ */
+
+/*
+ * Define defaults for cleanup code.
+ * Note: Unless the build explicitly defines one of the following, then
+ * we default to standard C style cleanup. This style uses setjmp/longjmp
+ * in the cancelation and thread exit implementations and therefore won't
+ * do stack unwinding if linked to applications that have it (e.g.
+ * C++ apps). This is currently consistent with most/all commercial Unix
+ * POSIX threads implementations.
+ */
+#if !defined( __CLEANUP_SEH ) && !defined( __CLEANUP_CXX ) && !defined( __CLEANUP_C )
+# define __CLEANUP_C
+#endif
+
+#if defined( __CLEANUP_SEH ) && defined(__GNUC__)
+#error ERROR [__FILE__, line __LINE__]: GNUC does not support SEH.
+#endif
+
+typedef struct ptw32_cleanup_t ptw32_cleanup_t;
+typedef void (__cdecl *ptw32_cleanup_callback_t)(void *);
+
+struct ptw32_cleanup_t
+{
+  ptw32_cleanup_callback_t routine;
+  void *arg;
+  struct ptw32_cleanup_t *prev;
+};
+
+#ifdef __CLEANUP_SEH
+	/*
+	 * WIN32 SEH version of cancel cleanup.
+	 */
+
+#define pthread_cleanup_push( _rout, _arg ) \
+	{ \
+		ptw32_cleanup_t	_cleanup; \
+		\
+	_cleanup.routine	= (ptw32_cleanup_callback_t)(_rout); \
+		_cleanup.arg	= (_arg); \
+		__try \
+		  { \
+
+#define pthread_cleanup_pop( _execute ) \
+		  } \
+		__finally \
+		{ \
+			if( _execute || AbnormalTermination()) \
+			  { \
+			  (*(_cleanup.routine))( _cleanup.arg ); \
+			  } \
+		} \
+	}
+
+#else /* __CLEANUP_SEH */
+
+#ifdef __CLEANUP_C
+
+	/*
+	 * C implementation of PThreads cancel cleanup
+	 */
+
+#define pthread_cleanup_push( _rout, _arg ) \
+	{ \
+		ptw32_cleanup_t	_cleanup; \
+		\
+		ptw32_push_cleanup( &_cleanup, (ptw32_cleanup_callback_t) (_rout), (_arg) ); \
+
+#define pthread_cleanup_pop( _execute ) \
+		(void) ptw32_pop_cleanup( _execute ); \
+	}
+
+#else /* __CLEANUP_C */
+
+#ifdef __CLEANUP_CXX
+
+	/*
+	 * C++ version of cancel cleanup.
+	 * - John E. Bossom.
+	 */
+
+	class PThreadCleanup {
+	  /*
+	   * PThreadCleanup
+	   *
+	   * Purpose
+	   *	  This class is a C++ helper class that is
+	   *	  used to implement pthread_cleanup_push/
+	   *	  pthread_cleanup_pop.
+	   *	  The destructor of this class automatically
+	   *	  pops the pushed cleanup routine regardless
+	   *	  of how the code exits the scope
+	   *	  (i.e. such as by an exception)
+	   */
+	  ptw32_cleanup_callback_t cleanUpRout;
+	  void	  *	  obj;
+	  int		  executeIt;
+
+	public:
+	  PThreadCleanup() :
+		cleanUpRout( 0 ),
+		obj( 0 ),
+		executeIt( 0 )
+		/*
+		 * No cleanup performed
+		 */
+		{
+		}
+
+	  PThreadCleanup(
+		 ptw32_cleanup_callback_t routine,
+			 void	 *	 arg ) :
+		cleanUpRout( routine ),
+		obj( arg ),
+		executeIt( 1 )
+		/*
+		 * Registers a cleanup routine for 'arg'
+		 */
+		{
+		}
+
+	  ~PThreadCleanup()
+		{
+		  if ( executeIt && ((void *) cleanUpRout != (void *) 0) )
+		{
+		  (void) (*cleanUpRout)( obj );
+		}
+		}
+
+	  void execute( int exec )
+		{
+		  executeIt = exec;
+		}
+	};
+
+	/*
+	 * C++ implementation of PThreads cancel cleanup;
+	 * This implementation takes advantage of a helper
+	 * class who's destructor automatically calls the
+	 * cleanup routine if we exit our scope weirdly
+	 */
+#define pthread_cleanup_push( _rout, _arg ) \
+	{ \
+		PThreadCleanup  cleanup((ptw32_cleanup_callback_t)(_rout), \
+					(void *) (_arg) );
+
+#define pthread_cleanup_pop( _execute ) \
+		cleanup.execute( _execute ); \
+	}
+
+#else
+
+#error ERROR [__FILE__, line __LINE__]: Cleanup type undefined.
+
+#endif /* __CLEANUP_CXX */
+
+#endif /* __CLEANUP_C */
+
+#endif /* __CLEANUP_SEH */
+
+/*
+ * ===============
+ * ===============
+ * Methods
+ * ===============
+ * ===============
+ */
+
+/*
+ * PThread Attribute Functions
+ */
+PTW32_DLLPORT int pthread_attr_init (pthread_attr_t * attr);
+
+PTW32_DLLPORT int pthread_attr_destroy (pthread_attr_t * attr);
+
+PTW32_DLLPORT int pthread_attr_getdetachstate (const pthread_attr_t * attr,
+					 int *detachstate);
+
+PTW32_DLLPORT int pthread_attr_getstackaddr (const pthread_attr_t * attr,
+					   void **stackaddr);
+
+PTW32_DLLPORT int pthread_attr_getstacksize (const pthread_attr_t * attr,
+					   size_t * stacksize);
+
+PTW32_DLLPORT int pthread_attr_setdetachstate (pthread_attr_t * attr,
+					 int detachstate);
+
+PTW32_DLLPORT int pthread_attr_setstackaddr (pthread_attr_t * attr,
+					   void *stackaddr);
+
+PTW32_DLLPORT int pthread_attr_setstacksize (pthread_attr_t * attr,
+					   size_t stacksize);
+
+PTW32_DLLPORT int pthread_attr_getschedparam (const pthread_attr_t *attr,
+					struct sched_param *param);
+
+PTW32_DLLPORT int pthread_attr_setschedparam (pthread_attr_t *attr,
+					const struct sched_param *param);
+
+PTW32_DLLPORT int pthread_attr_setschedpolicy (pthread_attr_t *,
+					 int);
+
+PTW32_DLLPORT int pthread_attr_getschedpolicy (pthread_attr_t *,
+					 int *);
+
+PTW32_DLLPORT int pthread_attr_setinheritsched(pthread_attr_t * attr,
+					 int inheritsched);
+
+PTW32_DLLPORT int pthread_attr_getinheritsched(pthread_attr_t * attr,
+					 int * inheritsched);
+
+PTW32_DLLPORT int pthread_attr_setscope (pthread_attr_t *,
+				   int);
+
+PTW32_DLLPORT int pthread_attr_getscope (const pthread_attr_t *,
+				   int *);
+
+/*
+ * PThread Functions
+ */
+PTW32_DLLPORT int pthread_create (pthread_t * tid,
+				const pthread_attr_t * attr,
+				void *(*start) (void *),
+				void *arg);
+
+PTW32_DLLPORT int pthread_detach (pthread_t tid);
+
+PTW32_DLLPORT int pthread_equal (pthread_t t1,
+			   pthread_t t2);
+
+PTW32_DLLPORT void pthread_exit (void *value_ptr);
+
+PTW32_DLLPORT int pthread_join (pthread_t thread,
+			  void **value_ptr);
+
+PTW32_DLLPORT pthread_t pthread_self (void);
+
+PTW32_DLLPORT int pthread_cancel (pthread_t thread);
+
+PTW32_DLLPORT int pthread_setcancelstate (int state,
+					int *oldstate);
+
+PTW32_DLLPORT int pthread_setcanceltype (int type,
+				   int *oldtype);
+
+PTW32_DLLPORT void pthread_testcancel (void);
+
+PTW32_DLLPORT int pthread_once (pthread_once_t * once_control,
+			  void (*init_routine) (void));
+
+#if PTW32_LEVEL >= PTW32_LEVEL_MAX
+PTW32_DLLPORT ptw32_cleanup_t *ptw32_pop_cleanup (int execute);
+
+PTW32_DLLPORT void ptw32_push_cleanup (ptw32_cleanup_t * cleanup,
+				 void (*routine) (void *),
+				 void *arg);
+#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */
+
+/*
+ * Thread Specific Data Functions
+ */
+PTW32_DLLPORT int pthread_key_create (pthread_key_t * key,
+				void (*destructor) (void *));
+
+PTW32_DLLPORT int pthread_key_delete (pthread_key_t key);
+
+PTW32_DLLPORT int pthread_setspecific (pthread_key_t key,
+				 const void *value);
+
+PTW32_DLLPORT void *pthread_getspecific (pthread_key_t key);
+
+
+/*
+ * Mutex Attribute Functions
+ */
+PTW32_DLLPORT int pthread_mutexattr_init (pthread_mutexattr_t * attr);
+
+PTW32_DLLPORT int pthread_mutexattr_destroy (pthread_mutexattr_t * attr);
+
+PTW32_DLLPORT int pthread_mutexattr_getpshared (const pthread_mutexattr_t
+					  * attr,
+					  int *pshared);
+
+PTW32_DLLPORT int pthread_mutexattr_setpshared (pthread_mutexattr_t * attr,
+					  int pshared);
+
+PTW32_DLLPORT int pthread_mutexattr_settype (pthread_mutexattr_t * attr, int kind);
+PTW32_DLLPORT int pthread_mutexattr_gettype (pthread_mutexattr_t * attr, int *kind);
+
+/*
+ * Barrier Attribute Functions
+ */
+PTW32_DLLPORT int pthread_barrierattr_init (pthread_barrierattr_t * attr);
+
+PTW32_DLLPORT int pthread_barrierattr_destroy (pthread_barrierattr_t * attr);
+
+PTW32_DLLPORT int pthread_barrierattr_getpshared (const pthread_barrierattr_t
+						* attr,
+						int *pshared);
+
+PTW32_DLLPORT int pthread_barrierattr_setpshared (pthread_barrierattr_t * attr,
+						int pshared);
+
+/*
+ * Mutex Functions
+ */
+PTW32_DLLPORT int pthread_mutex_init (pthread_mutex_t * mutex,
+				const pthread_mutexattr_t * attr);
+
+PTW32_DLLPORT int pthread_mutex_destroy (pthread_mutex_t * mutex);
+
+PTW32_DLLPORT int pthread_mutex_lock (pthread_mutex_t * mutex);
+
+PTW32_DLLPORT int pthread_mutex_timedlock(pthread_mutex_t *mutex,
+					const struct timespec *abstime);
+
+PTW32_DLLPORT int pthread_mutex_trylock (pthread_mutex_t * mutex);
+
+PTW32_DLLPORT int pthread_mutex_unlock (pthread_mutex_t * mutex);
+
+/*
+ * Spinlock Functions
+ */
+PTW32_DLLPORT int pthread_spin_init (pthread_spinlock_t * lock, int pshared);
+
+PTW32_DLLPORT int pthread_spin_destroy (pthread_spinlock_t * lock);
+
+PTW32_DLLPORT int pthread_spin_lock (pthread_spinlock_t * lock);
+
+PTW32_DLLPORT int pthread_spin_trylock (pthread_spinlock_t * lock);
+
+PTW32_DLLPORT int pthread_spin_unlock (pthread_spinlock_t * lock);
+
+/*
+ * Barrier Functions
+ */
+PTW32_DLLPORT int pthread_barrier_init (pthread_barrier_t * barrier,
+				  const pthread_barrierattr_t * attr,
+				  unsigned int count);
+
+PTW32_DLLPORT int pthread_barrier_destroy (pthread_barrier_t * barrier);
+
+PTW32_DLLPORT int pthread_barrier_wait (pthread_barrier_t * barrier);
+
+/*
+ * Condition Variable Attribute Functions
+ */
+PTW32_DLLPORT int pthread_condattr_init (pthread_condattr_t * attr);
+
+PTW32_DLLPORT int pthread_condattr_destroy (pthread_condattr_t * attr);
+
+PTW32_DLLPORT int pthread_condattr_getpshared (const pthread_condattr_t * attr,
+					 int *pshared);
+
+PTW32_DLLPORT int pthread_condattr_setpshared (pthread_condattr_t * attr,
+					 int pshared);
+
+/*
+ * Condition Variable Functions
+ */
+PTW32_DLLPORT int pthread_cond_init (pthread_cond_t * cond,
+				   const pthread_condattr_t * attr);
+
+PTW32_DLLPORT int pthread_cond_destroy (pthread_cond_t * cond);
+
+PTW32_DLLPORT int pthread_cond_wait (pthread_cond_t * cond,
+				   pthread_mutex_t * mutex);
+
+PTW32_DLLPORT int pthread_cond_timedwait (pthread_cond_t * cond,
+					pthread_mutex_t * mutex,
+					const struct timespec *abstime);
+
+PTW32_DLLPORT int pthread_cond_signal (pthread_cond_t * cond);
+
+PTW32_DLLPORT int pthread_cond_broadcast (pthread_cond_t * cond);
+
+/*
+ * Scheduling
+ */
+PTW32_DLLPORT int pthread_setschedparam (pthread_t thread,
+				   int policy,
+				   const struct sched_param *param);
+
+PTW32_DLLPORT int pthread_getschedparam (pthread_t thread,
+				   int *policy,
+				   struct sched_param *param);
+
+PTW32_DLLPORT int pthread_setconcurrency (int);
+ 
+PTW32_DLLPORT int pthread_getconcurrency (void);
+
+/*
+ * Read-Write Lock Functions
+ */
+PTW32_DLLPORT int pthread_rwlock_init(pthread_rwlock_t *lock,
+				const pthread_rwlockattr_t *attr);
+
+PTW32_DLLPORT int pthread_rwlock_destroy(pthread_rwlock_t *lock);
+
+PTW32_DLLPORT int pthread_rwlock_tryrdlock(pthread_rwlock_t *);
+
+PTW32_DLLPORT int pthread_rwlock_trywrlock(pthread_rwlock_t *);
+
+PTW32_DLLPORT int pthread_rwlock_rdlock(pthread_rwlock_t *lock);
+
+PTW32_DLLPORT int pthread_rwlock_timedrdlock(pthread_rwlock_t *lock,
+					   const struct timespec *abstime);
+
+PTW32_DLLPORT int pthread_rwlock_wrlock(pthread_rwlock_t *lock);
+
+PTW32_DLLPORT int pthread_rwlock_timedwrlock(pthread_rwlock_t *lock,
+					   const struct timespec *abstime);
+
+PTW32_DLLPORT int pthread_rwlock_unlock(pthread_rwlock_t *lock);
+
+PTW32_DLLPORT int pthread_rwlockattr_init (pthread_rwlockattr_t * attr);
+
+PTW32_DLLPORT int pthread_rwlockattr_destroy (pthread_rwlockattr_t * attr);
+
+PTW32_DLLPORT int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t * attr,
+					   int *pshared);
+
+PTW32_DLLPORT int pthread_rwlockattr_setpshared (pthread_rwlockattr_t * attr,
+					   int pshared);
+
+/*
+ *  Errno reading function
+ *  Added by Paul Pedriana, 10/2003 
+ */
+PTW32_DLLPORT int pthread_win32_errno(void);
+
+
+#if PTW32_LEVEL >= PTW32_LEVEL_MAX - 1
+
+/*
+ * Signal Functions. Should be defined in <signal.h> but MSVC and MinGW32
+ * already have signal.h that don't define these.
+ */
+PTW32_DLLPORT int pthread_kill(pthread_t thread, int sig);
+
+/*
+ * Non-portable functions
+ */
+
+/*
+ * Compatibility with Linux.
+ */
+PTW32_DLLPORT int pthread_mutexattr_setkind_np(pthread_mutexattr_t * attr,
+					 int kind);
+PTW32_DLLPORT int pthread_mutexattr_getkind_np(pthread_mutexattr_t * attr,
+					 int *kind);
+
+/*
+ * Possibly supported by other POSIX threads implementations
+ */
+PTW32_DLLPORT int pthread_delay_np (struct timespec * interval);
+PTW32_DLLPORT int pthread_num_processors_np(void);
+
+/*
+ * Useful if an application wants to statically link
+ * the lib rather than load the DLL at run-time.
+ */
+PTW32_DLLPORT int pthread_win32_process_attach_np(void);
+PTW32_DLLPORT int pthread_win32_process_detach_np(void);
+PTW32_DLLPORT int pthread_win32_thread_attach_np(void);
+PTW32_DLLPORT int pthread_win32_thread_detach_np(void);
+
+/*
+ * Register a system time change with the library.
+ * Causes the library to perform various functions
+ * in response to the change. Should be called whenever
+ * the application's top level window receives a
+ * WM_TIMECHANGE message. It can be passed directly to
+ * pthread_create() as a new thread if desired.
+ */
+PTW32_DLLPORT void * pthread_timechange_handler_np(void *);
+
+#endif /*PTW32_LEVEL >= PTW32_LEVEL_MAX - 1 */
+
+#if PTW32_LEVEL >= PTW32_LEVEL_MAX
+
+/*
+ * Returns the Win32 HANDLE for the POSIX thread.
+ */
+PTW32_DLLPORT HANDLE pthread_getw32threadhandle_np(pthread_t thread);
+
+
+/*
+ * Protected Methods
+ *
+ * This function blocks until the given WIN32 handle
+ * is signaled or pthread_cancel had been called.
+ * This function allows the caller to hook into the
+ * PThreads cancel mechanism. It is implemented using
+ *
+ *		WaitForMultipleObjects
+ *
+ * on 'waitHandle' and a manually reset WIN32 Event
+ * used to implement pthread_cancel. The 'timeout'
+ * argument to TimedWait is simply passed to
+ * WaitForMultipleObjects.
+ */
+PTW32_DLLPORT int pthreadCancelableWait (HANDLE waitHandle);
+PTW32_DLLPORT int pthreadCancelableTimedWait (HANDLE waitHandle,
+					DWORD timeout);
+
+#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */
+
+/*
+ * Thread-Safe C Runtime Library Mappings.
+ */
+#ifndef _UWIN
+#  if defined(NEED_ERRNO)
+	 PTW32_DLLPORT int * _errno( void );
+#  else
+#    ifndef errno
+#      if (defined(_MT) || defined(_DLL))
+	 __declspec(dllimport) extern int * __cdecl _errno(void);
+#	 define errno	(*_errno())
+#      endif
+#    endif
+#  endif
+#endif
+
+/*
+ * WIN32 C runtime library had been made thread-safe
+ * without affecting the user interface. Provide
+ * mappings from the UNIX thread-safe versions to
+ * the standard C runtime library calls.
+ * Only provide function mappings for functions that
+ * actually exist on WIN32.
+ */
+
+#if !defined(__MINGW32__)
+#define strtok_r( _s, _sep, _lasts ) \
+	( *(_lasts) = strtok( (_s), (_sep) ) )
+#endif /* !__MINGW32__ */
+
+#define asctime_r( _tm, _buf ) \
+	( strcpy( (_buf), asctime( (_tm) ) ), \
+	  (_buf) )
+
+#define ctime_r( _clock, _buf ) \
+	( strcpy( (_buf), ctime( (_clock) ) ),	\
+	  (_buf) )
+
+#define gmtime_r( _clock, _result ) \
+	( *(_result) = *gmtime( (_clock) ), \
+	  (_result) )
+
+#define localtime_r( _clock, _result ) \
+	( *(_result) = *localtime( (_clock) ), \
+	  (_result) )
+
+#define rand_r( _seed ) \
+	( _seed == _seed? rand() : rand() )
+
+
+#ifdef __cplusplus
+
+/*
+ * Internal exceptions
+ */
+class ptw32_exception {};
+class ptw32_exception_cancel : public ptw32_exception {};
+class ptw32_exception_exit   : public ptw32_exception {};
+
+#endif
+
+#if PTW32_LEVEL >= PTW32_LEVEL_MAX
+
+/* FIXME: This is only required if the library was built using SEH */
+/*
+ * Get internal SEH tag
+ */
+PTW32_DLLPORT DWORD ptw32_get_exception_services_code(void);
+
+#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */
+
+#ifndef PTW32_BUILD
+
+#ifdef __CLEANUP_SEH
+
+/*
+ * Redefine the SEH __except keyword to ensure that applications
+ * propagate our internal exceptions up to the library's internal handlers.
+ */
+#define __except( E ) \
+	__except( ( GetExceptionCode() == ptw32_get_exception_services_code() ) \
+		 ? EXCEPTION_CONTINUE_SEARCH : ( E ) )
+
+#endif /* __CLEANUP_SEH */
+
+#ifdef __CLEANUP_CXX
+
+/*
+ * Redefine the C++ catch keyword to ensure that applications
+ * propagate our internal exceptions up to the library's internal handlers.
+ */
+#ifdef _MSC_VER
+	/*
+	 * WARNING: Replace any 'catch( ... )' with 'PtW32CatchAll'
+	 * if you want Pthread-Win32 cancelation and pthread_exit to work.
+	 */
+
+#ifndef PtW32NoCatchWarn
+
+#pragma message("Specify \"/DPtW32NoCatchWarn\" compiler flag to skip this message.")
+#pragma message("------------------------------------------------------------------")
+#pragma message("When compiling applications with MSVC++ and C++ exception handling:")
+#pragma message("  Replace any 'catch( ... )' in routines called from POSIX threads")
+#pragma message("  with 'PtW32CatchAll' or 'CATCHALL' if you want POSIX thread")
+#pragma message("  cancelation and pthread_exit to work. For example:")
+#pragma message("")
+#pragma message("    #ifdef PtW32CatchAll")
+#pragma message("      PtW32CatchAll")
+#pragma message("    #else")
+#pragma message("      catch(...)")
+#pragma message("    #endif")
+#pragma message("	 {")
+#pragma message("	   /* Catchall block processing */")
+#pragma message("	 }")
+#pragma message("------------------------------------------------------------------")
+
+#endif
+
+#define PtW32CatchAll \
+	catch( ptw32_exception & ) { throw; } \
+	catch( ... )
+
+#else /* _MSC_VER */
+
+#define catch( E ) \
+	catch( ptw32_exception & ) { throw; } \
+	catch( E )
+
+#endif /* _MSC_VER */
+
+#endif /* __CLEANUP_CXX */
+
+#endif /* ! PTW32_BUILD */
+
+#ifdef __cplusplus
+}				/* End of extern "C" */
+#endif				/* __cplusplus */
+
+#ifdef PTW32__HANDLE_DEF
+# undef HANDLE
+#endif
+#ifdef PTW32__DWORD_DEF
+# undef DWORD
+#endif
+
+#undef PTW32_LEVEL
+#undef PTW32_LEVEL_MAX
+
+#endif /* PTHREAD_H */

+ 174 - 0
test/thread/lib/pthreads-win32/include/sched.h

@@ -0,0 +1,174 @@
+/*
+ * Module: sched.h
+ *
+ * Purpose:
+ *      Provides an implementation of POSIX realtime extensions
+ *      as defined in 
+ *
+ *              POSIX 1003.1b-1993      (POSIX.1b)
+ *
+ * --------------------------------------------------------------------------
+ *
+ *      Pthreads-win32 - POSIX Threads Library for Win32
+ *      Copyright(C) 1998 John E. Bossom
+ *      Copyright(C) 1999,2003 Pthreads-win32 contributors
+ * 
+ *      Contact Email: [email protected]
+ * 
+ *      The current list of contributors is contained
+ *      in the file CONTRIBUTORS included with the source
+ *      code distribution. The list can also be seen at the
+ *      following World Wide Web location:
+ *      http://sources.redhat.com/pthreads-win32/contributors.html
+ * 
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation; either
+ *      version 2 of the License, or (at your option) any later version.
+ * 
+ *      This library is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *      Lesser General Public License for more details.
+ * 
+ *      You should have received a copy of the GNU Lesser General Public
+ *      License along with this library in the file COPYING.LIB;
+ *      if not, write to the Free Software Foundation, Inc.,
+ *      59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+#ifndef _SCHED_H
+#define _SCHED_H
+
+#undef PTW32_LEVEL
+
+#if defined(_POSIX_SOURCE)
+#define PTW32_LEVEL 0
+/* Early POSIX */
+#endif
+
+#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309
+#undef PTW32_LEVEL
+#define PTW32_LEVEL 1
+/* Include 1b, 1c and 1d */
+#endif
+
+#if defined(INCLUDE_NP)
+#undef PTW32_LEVEL
+#define PTW32_LEVEL 2
+/* Include Non-Portable extensions */
+#endif
+
+#define PTW32_LEVEL_MAX 3
+
+#if !defined(PTW32_LEVEL)
+#define PTW32_LEVEL PTW32_LEVEL_MAX
+/* Include everything */
+#endif
+
+
+#if __GNUC__ && ! defined (__declspec)
+# error Please upgrade your GNU compiler to one that supports __declspec.
+#endif
+
+/*
+ * When building the DLL code, you should define PTW32_BUILD so that
+ * the variables/functions are exported correctly. When using the DLL,
+ * do NOT define PTW32_BUILD, and then the variables/functions will
+ * be imported correctly.
+ */
+#ifdef PTW32_BUILD
+# define PTW32_DLLPORT __declspec (dllexport)
+#else
+# define PTW32_DLLPORT __declspec (dllimport)
+#endif
+
+/*
+ * This is a duplicate of what is in the autoconf config.h,
+ * which is only used when building the pthread-win32 libraries.
+ */
+
+#ifndef PTW32_CONFIG_H
+#  if defined(WINCE)
+#    define NEED_ERRNO
+#    define NEED_SEM
+#  endif
+#  if defined(_UWIN) || defined(__MINGW32__)
+#    define HAVE_MODE_T
+#  endif
+#endif
+
+/*
+ *
+ */
+
+#if PTW32_LEVEL >= PTW32_LEVEL_MAX
+#ifdef NEED_ERRNO
+#include "need_errno.h"
+#else
+#include <errno.h>
+#endif
+#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */
+
+#if defined(__MINGW32__) || defined(_UWIN)
+#if PTW32_LEVEL >= PTW32_LEVEL_MAX
+/* For pid_t */
+#  include <sys/types.h>
+/* Required by Unix 98 */
+#  include <time.h>
+#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */
+#else
+typedef int pid_t;
+#endif
+
+/* Thread scheduling policies */
+
+enum {
+  SCHED_OTHER = 0,
+  SCHED_FIFO,
+  SCHED_RR,
+  SCHED_MIN   = SCHED_OTHER,
+  SCHED_MAX   = SCHED_RR
+};
+
+struct sched_param {
+  int sched_priority;
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif                          /* __cplusplus */
+
+PTW32_DLLPORT int sched_yield (void);
+
+PTW32_DLLPORT int sched_get_priority_min (int policy);
+
+PTW32_DLLPORT int sched_get_priority_max (int policy);
+
+PTW32_DLLPORT int sched_setscheduler (pid_t pid, int policy);
+
+PTW32_DLLPORT int sched_getscheduler (pid_t pid);
+
+/*
+ * Note that this macro returns ENOTSUP rather than
+ * ENOSYS as might be expected. However, returning ENOSYS
+ * should mean that sched_get_priority_{min,max} are
+ * not implemented as well as sched_rr_get_interval.
+ * This is not the case, since we just don't support
+ * round-robin scheduling. Therefore I have chosen to
+ * return the same value as sched_setscheduler when
+ * SCHED_RR is passed to it.
+ */
+#define sched_rr_get_interval(_pid, _interval) \
+  ( errno = ENOTSUP, (int) -1 )
+
+
+#ifdef __cplusplus
+}                               /* End of extern "C" */
+#endif                          /* __cplusplus */
+
+#undef PTW32_LEVEL
+#undef PTW32_LEVEL_MAX
+
+#endif                          /* !_SCHED_H */
+

+ 163 - 0
test/thread/lib/pthreads-win32/include/semaphore.h

@@ -0,0 +1,163 @@
+/*
+ * Module: semaphore.h
+ *
+ * Purpose:
+ *	Semaphores aren't actually part of the PThreads standard.
+ *	They are defined by the POSIX Standard:
+ *
+ *		POSIX 1003.1b-1993	(POSIX.1b)
+ *
+ * --------------------------------------------------------------------------
+ *
+ *      Pthreads-win32 - POSIX Threads Library for Win32
+ *      Copyright(C) 1998 John E. Bossom
+ *      Copyright(C) 1999,2003 Pthreads-win32 contributors
+ * 
+ *      Contact Email: [email protected]
+ * 
+ *      The current list of contributors is contained
+ *      in the file CONTRIBUTORS included with the source
+ *      code distribution. The list can also be seen at the
+ *      following World Wide Web location:
+ *      http://sources.redhat.com/pthreads-win32/contributors.html
+ * 
+ *      This library is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU Lesser General Public
+ *      License as published by the Free Software Foundation; either
+ *      version 2 of the License, or (at your option) any later version.
+ * 
+ *      This library is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *      Lesser General Public License for more details.
+ * 
+ *      You should have received a copy of the GNU Lesser General Public
+ *      License along with this library in the file COPYING.LIB;
+ *      if not, write to the Free Software Foundation, Inc.,
+ *      59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+#if !defined( SEMAPHORE_H )
+#define SEMAPHORE_H
+
+#undef PTW32_LEVEL
+
+#if defined(_POSIX_SOURCE)
+#define PTW32_LEVEL 0
+/* Early POSIX */
+#endif
+
+#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309
+#undef PTW32_LEVEL
+#define PTW32_LEVEL 1
+/* Include 1b, 1c and 1d */
+#endif
+
+#if defined(INCLUDE_NP)
+#undef PTW32_LEVEL
+#define PTW32_LEVEL 2
+/* Include Non-Portable extensions */
+#endif
+
+#define PTW32_LEVEL_MAX 3
+
+#if !defined(PTW32_LEVEL)
+#define PTW32_LEVEL PTW32_LEVEL_MAX
+/* Include everything */
+#endif
+
+#if __GNUC__ && ! defined (__declspec)
+# error Please upgrade your GNU compiler to one that supports __declspec.
+#endif
+
+/*
+ * When building the DLL code, you should define PTW32_BUILD so that
+ * the variables/functions are exported correctly. When using the DLL,
+ * do NOT define PTW32_BUILD, and then the variables/functions will
+ * be imported correctly.
+ */
+#ifdef PTW32_BUILD
+# define PTW32_DLLPORT __declspec (dllexport)
+#else
+# define PTW32_DLLPORT __declspec (dllimport)
+#endif
+
+
+/*
+ * This is a duplicate of what is in the autoconf config.h,
+ * which is only used when building the pthread-win32 libraries.
+ */
+
+#ifndef PTW32_CONFIG_H
+#  if defined(WINCE)
+#    define NEED_ERRNO
+#    define NEED_SEM
+#  endif
+#  if defined(_UWIN) || defined(__MINGW32__)
+#    define HAVE_MODE_T
+#  endif
+#endif
+
+/*
+ *
+ */
+
+#if PTW32_LEVEL >= PTW32_LEVEL_MAX
+#ifdef NEED_ERRNO
+#include "need_errno.h"
+#else
+#include <errno.h>
+#endif
+#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */
+
+#define _POSIX_SEMAPHORES
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif				/* __cplusplus */
+
+#ifndef HAVE_MODE_T
+typedef unsigned int mode_t;
+#endif
+
+
+typedef struct sem_t_ * sem_t;
+
+PTW32_DLLPORT int sem_init (sem_t * sem,
+				int pshared,
+				unsigned int value);
+
+PTW32_DLLPORT int sem_destroy (sem_t * sem);
+
+PTW32_DLLPORT int sem_trywait (sem_t * sem);
+
+PTW32_DLLPORT int sem_wait (sem_t * sem);
+
+PTW32_DLLPORT int sem_timedwait (sem_t * sem,
+				 const struct timespec * abstime);
+
+PTW32_DLLPORT int sem_post (sem_t * sem);
+
+PTW32_DLLPORT int sem_post_multiple (sem_t * sem,
+					 int count);
+
+PTW32_DLLPORT int sem_open (const char * name,
+				int oflag,
+				mode_t mode,
+				unsigned int value);
+
+PTW32_DLLPORT int sem_close (sem_t * sem);
+
+PTW32_DLLPORT int sem_unlink (const char * name);
+
+PTW32_DLLPORT int sem_getvalue (sem_t * sem,
+				int * sval);
+
+#ifdef __cplusplus
+}				/* End of extern "C" */
+#endif				/* __cplusplus */
+
+#undef PTW32_LEVEL
+#undef PTW32_LEVEL_MAX
+
+#endif				/* !SEMAPHORE_H */

BIN
test/thread/lib/pthreads-win32/lib/pthreadVC.dll


BIN
test/thread/lib/pthreads-win32/lib/pthreadVC.lib


BIN
test/thread/lib/pthreads-win32/lib/pthreadVC.pdb


+ 242 - 0
test/thread/source/TestEnumerateThreads.cpp

@@ -0,0 +1,242 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include <EATest/EATest.h>
+#include "TestThread.h"
+#include <eathread/eathread_thread.h>
+#include <eathread/eathread_semaphore.h>
+#include <eathread/eathread_sync.h>
+#include <eathread/eathread_atomic.h>
+
+EA_DISABLE_ALL_VC_WARNINGS()
+#include <string.h>
+EA_RESTORE_ALL_VC_WARNINGS()
+
+using namespace EA::Thread;
+using namespace EA::Thread::detail;
+using namespace EA::UnitTest;
+
+//-------------------------------------------------------------------------------
+// Globals
+static Semaphore gSemaphore;
+
+//-------------------------------------------------------------------------------
+//
+static intptr_t TestFunction1(void*)
+{
+	// Wait until we are signaled by the unit test to complete.
+	gSemaphore.Wait();
+	return 0;
+}
+
+//-------------------------------------------------------------------------------
+//
+int TestSimpleEnumerateThreads()
+{
+	int nErrorCount = 0;
+	static EA::Thread::AtomicInt<size_t> snThreadStartCount;
+	snThreadStartCount = 0;
+
+	auto threadEntry = [](void*) -> intptr_t
+	{
+		snThreadStartCount++;
+
+		// Wait until we are signaled by the unit test to complete.
+		gSemaphore.Wait();
+		return 0;
+	};
+
+	const size_t kMaxTestThreadEnumCount = 16;
+	ThreadEnumData enumData[kMaxTestThreadEnumCount];
+
+	// Prevents all threads from returning.    
+	gSemaphore.Init(0); 
+
+	// Startup all the threads we want to monitor.
+	Thread threads[kMaxTestThreadEnumCount];
+	for(size_t i = 0; i < kMaxTestThreadEnumCount; i++) 
+	{        
+		threads[i].Begin(threadEntry);
+	}
+
+	// Give all the threads a chance to start up. 
+	while(snThreadStartCount != kMaxTestThreadEnumCount)
+		EA::Thread::ThreadSleep(0);  
+
+	// Enumerate the active threads
+	size_t threadCount = EA::Thread::EnumerateThreads(enumData, EAArrayCount(enumData));
+	EATEST_VERIFY_MSG(threadCount >= kMaxTestThreadEnumCount, "Incorrect number of threads reported.");
+	// Report("Enumerated (at least) %d threads. Found (%d).\n", kMaxTestThreadEnumCount, threadCount);
+
+	for(size_t j = 0; j < kMaxTestThreadEnumCount; j++)
+	{   
+		//Report("\tThread id: %s\n", ThreadIdToStringBuffer(enumData[j].mpThreadDynamicData->mhThread).c_str());
+		if(enumData[j].mpThreadDynamicData == NULL)
+			continue;
+
+		if(strcmp(enumData[j].mpThreadDynamicData->mName, "external") != 0) // Disabled because we can't guarantee across all platforms that a stack base is available.  This will be fixed in a future release.
+		{
+			EATEST_VERIFY_MSG(enumData[j].mpThreadDynamicData->mpStackBase != NULL, "All thread meta data is expected to have the stack base address.");
+		}
+		enumData[j].Release(); 
+	}
+	
+	// Signal the threads to complete.
+	gSemaphore.Post(kMaxTestThreadEnumCount);
+
+	// Wait for all threads to complete.
+	for(size_t i = 0; i < kMaxTestThreadEnumCount; i++)
+	{
+		if(threads[i].GetStatus() != Thread::kStatusEnded)
+			threads[i].WaitForEnd();
+	}
+
+	return nErrorCount;
+}
+
+//-------------------------------------------------------------------------------
+//
+int TestSimpleEnumerateThreads_KillThreadsEarly()
+{
+	int nErrorCount = 0;
+
+	const size_t kMaxTestThreadEnumCount = 16;
+	ThreadEnumData enumData[kMaxTestThreadEnumCount];
+
+	// Prevents all threads from returning.    
+	gSemaphore.Init(0); 
+
+	// Startup all the threads we want to monitor.
+	Thread threads[kMaxTestThreadEnumCount];
+	for(size_t i = 0; i < kMaxTestThreadEnumCount; i++) 
+	{        
+		threads[i].Begin(TestFunction1);       
+	}
+	EA::Thread::ThreadSleep(300);  // Give all the threads a chance to start up. 
+
+	// Enumerate the active threads
+	size_t threadCount = EA::Thread::EnumerateThreads(enumData, EAArrayCount(enumData));
+	EATEST_VERIFY_MSG(threadCount >= kMaxTestThreadEnumCount, "Incorrect number of threads reported.");
+	// Report("Enumerated (at least) %d threads. Found (%d).\n", kMaxTestThreadEnumCount, threadCount);
+
+	// Signal the threads to complete.
+	gSemaphore.Post(kMaxTestThreadEnumCount);
+	EA::Thread::ThreadSleep(500);   
+
+	// Terminate the threads before the user explicitly releases them.
+	for(size_t i = 0; i < kMaxTestThreadEnumCount; i++)
+	{
+		if(threads[i].GetStatus() != Thread::kStatusEnded)
+			threads[i].WaitForEnd();
+	}
+
+	for(size_t j = 0; j < kMaxTestThreadEnumCount; j++)
+	{   
+		//Report("\tThread id: %s\n", ThreadIdToStringBuffer(enumData[j].mpThreadDynamicData->mhThread).c_str());
+		enumData[j].Release(); 
+	}
+
+	return nErrorCount;
+}
+
+//-------------------------------------------------------------------------------
+//
+int TestEnumerateThreads_EnumerateMain()
+{
+	int nErrorCount = 0;
+
+	const size_t kMaxTestThreadEnumCount = 16;
+	ThreadEnumData enumData[kMaxTestThreadEnumCount];
+
+	size_t threadCount = EA::Thread::EnumerateThreads(enumData, EAArrayCount(enumData));
+	EATEST_VERIFY_MSG(threadCount >= 1, "No threads found.  We are expecting at least the main thread to be reported.");
+
+	int compare_result = strcmp(enumData[0].mpThreadDynamicData->mName, "external");
+	EATEST_VERIFY_MSG(compare_result == 0, "Not an externally created thread.");
+
+#if EA_USE_CPP11_CONCURRENCY
+	ThreadUniqueId thisThreadId;
+	EAThreadGetUniqueId(thisThreadId);
+
+	ThreadUniqueId uniqueThreadId = enumData[0].mpThreadDynamicData->mUniqueThreadId;
+	EATEST_VERIFY_MSG(uniqueThreadId == thisThreadId, "Did not return the threadId of this call context.");  
+#elif defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE
+	ThreadId enumThreadId = enumData[0].mpThreadDynamicData->mhThread;  // No portable thread id available in the ThreadDynamicDataStructure.
+	EATEST_VERIFY_MSG(enumThreadId == EA::Thread::GetThreadId(), "Did not return the threadId of this call context.");  
+#else
+	ThreadId enumThreadId = enumData[0].mpThreadDynamicData->mThreadId;
+	EATEST_VERIFY_MSG(enumThreadId == EA::Thread::GetThreadId(), "Did not return the threadId of this call context.");  
+#endif
+
+	return nErrorCount;
+}
+
+//-------------------------------------------------------------------------------
+//
+EA_DISABLE_ALL_VC_WARNINGS()
+#include <thread>
+#include <chrono>
+#include <vector>
+#include <atomic>
+EA_RESTORE_ALL_VC_WARNINGS()
+#include <eathread/eathread_mutex.h>
+
+
+// TODO(rparolin):  This forces the build-farm to timeout.  Re-enable in the future.
+// 
+// int TestHeavyLoadThreadRegisteration()
+// {
+//     int nErrorCount = 0;
+
+// #ifdef EA_PLATFORM_MICROSOFT 
+//     // Only tested on Windows because its a reported regression in tools/pipeline related
+//     // technologies leveraging a large number of non-eathread threads which would exhaust internal
+//     // tracking system.
+//     std::atomic<bool> isDone = false;
+//     EA::Thread::Mutex s_mutex;
+
+//     {
+//         int loopCount = 170; // must exceed the value of EA::Thread::kMaxThreadDynamicDataCount.
+//         std::vector<std::thread> threads;
+//         while(loopCount--)  
+//         {
+//             threads.emplace_back(std::thread([&]
+//             {
+//                 while (!isDone)
+//                 {
+//                     // We lock an EA::Thread::Mutex because it used to force a 
+//                     // non-EAThread thread to be registered due to debug functionality
+//                     // requesting a thread id. Verify that locking a mutex no longer requires
+//                     // external thread registration by locking more threads that we can track.
+//                     EA::Thread::AutoMutex _(s_mutex);
+//                 }
+//             }));
+//         }
+
+//         std::this_thread::sleep_for(std::chrono::milliseconds(100));
+//         isDone = true;
+
+//         for (auto& th : threads)
+//             th.join();
+//     }
+// #endif
+
+//     return nErrorCount;
+// }
+
+
+//-------------------------------------------------------------------------------
+//
+int TestEnumerateThreads()
+{
+	int nErrorCount = 0;
+
+	nErrorCount += TestSimpleEnumerateThreads();
+	nErrorCount += TestSimpleEnumerateThreads_KillThreadsEarly();
+	nErrorCount += TestEnumerateThreads_EnumerateMain();
+	// nErrorCount += TestHeavyLoadThreadRegisteration();
+
+	return nErrorCount;
+}
+

+ 223 - 0
test/thread/source/TestThread.cpp

@@ -0,0 +1,223 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+
+#include "TestThread.h"
+#include <eathread/eathread.h>
+#include <eathread/eathread_callstack.h>
+#include <EATest/EATest.h>
+#include <EAStdC/EAString.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+//Prevents false positive memory leaks on GCC/Clang platforms
+#if defined(EA_COMPILER_GNUC) || defined(EA_COMPILER_CLANG)
+	#define EA_MEMORY_GCC_USE_FINALIZE
+#endif
+
+#include <MemoryMan/MemoryMan.inl>
+#include <MemoryMan/CoreAllocator.inl>
+#include <coreallocator/icoreallocator_interface.h>
+
+#ifdef EA_DLL
+	#include <MemoryMan/MemoryManDLL.h>
+#endif
+
+#if defined(EA_PLATFORM_MICROSOFT)
+	EA_DISABLE_ALL_VC_WARNINGS()
+	#include <Windows.h>
+	EA_RESTORE_ALL_VC_WARNINGS()
+#endif
+
+#if defined(EA_COMPILER_MSVC) && defined(EA_PLATFORM_MICROSOFT)
+	EA_DISABLE_ALL_VC_WARNINGS()
+	#include <crtdbg.h>
+	EA_RESTORE_ALL_VC_WARNINGS()
+#endif
+
+#include <EAMain/EAEntryPointMain.inl>
+#include <EATest/EASTLVsnprintf.inl>
+#include <EAMain/EAEntryPointMain.inl>
+
+
+///////////////////////////////////////////////////////////////////////////////
+// gTestLengthSeconds
+//
+unsigned int gTestLengthSeconds = 2;
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// EAThreadFailure
+//
+// This is called by EAThread's assert failure function.
+//
+static void EAThreadFailure(const char* pMessage, void* /*pContext*/)
+{
+	EA::UnitTest::IncrementGlobalErrorCount(1);
+	EA::UnitTest::Report("Thread test failure (EAThread assert): %s\n", pMessage);
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TestThreadMisc
+//
+// To do: Move this to its own file.
+//
+#if defined(EA_PLATFORM_APPLE)
+	#include <eathread/apple/eathread_callstack_apple.h>
+#endif
+
+#if defined(EA_PLATFORM_POSIX)
+#include <unistd.h>
+#endif
+
+bool IsSuperUser()
+{
+#if defined(EA_PLATFORM_POSIX) && !defined(EA_PLATFORM_SONY) && !defined(CS_UNDEFINED_STRING)  // PS4 is a POSIX machine but doesn't implement 'getuid'.
+	// http://pubs.opengroup.org/onlinepubs/009695399/functions/geteuid.html 
+	// http://pubs.opengroup.org/onlinepubs/009695399/functions/getuid.html
+	uid_t uid = getuid(), euid = geteuid();
+	return (uid == 0 || euid == 0);
+#else
+	return true;
+#endif
+}
+
+
+
+int TestThreadGetThreadTimeMin()
+{
+	int nErrorCount(0);
+#if defined(EA_PLATFORM_MICROSOFT) || defined(EA_PLATFORM_KETTLE)
+	EATEST_VERIFY_MSG(EA::Thread::GetThreadTime() >= EATHREAD_MIN_ABSOLUTE_TIME, "Reported GetThreadTime absolute time is less than EATHREAD_MIN_ABSOLUTE_TIME. You are going to have a bad time.");
+#endif
+	return nErrorCount;
+}
+
+int TestThreadMisc()
+{
+	int nErrorCount = 0;
+	// this is here because its intended to be the first test run. Since it depends on tick count since title start.
+	nErrorCount += TestThreadGetThreadTimeMin();
+
+	#if defined(EA_PLATFORM_APPLE) && EATHREAD_APPLE_GETMODULEINFO_ENABLED
+		if(!IsSuperUser())
+		{
+			EA::EAMain::Report("Skipping GetModuleInfoApple test because we don't have sufficient system privileges.\n");
+			return nErrorCount;
+		}
+
+		EA::Thread::ModuleInfoApple moduleInfoApple[15];
+		size_t n = EA::Thread::GetModuleInfoApple(moduleInfoApple, EAArrayCount(moduleInfoApple), NULL, true);
+
+		#if defined(EA_PLATFORM_OSX)
+			EATEST_VERIFY(n > 0);
+			for(size_t i = 0; i < eastl::min(n, EAArrayCount(moduleInfoApple)); i++)
+				EA::UnitTest::Report("%s\n", moduleInfoApple[i].mPath);
+		#else
+			EA_UNUSED(n);
+		#endif
+	#endif
+	
+	return nErrorCount;
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// EAMain
+//
+int EAMain(int argc, char** argv)
+{
+	using namespace EA::Thread;
+	using namespace EA::UnitTest;
+
+	int nErrorCount(0);
+
+	EA::EAMain::PlatformStartup();
+
+	gTestLengthSeconds = (unsigned int)(3 * EA::UnitTest::GetSystemSpeed());
+	if(gTestLengthSeconds == 0)
+		gTestLengthSeconds = 1;
+
+	// Set EAThread to route its errors to our own error reporting function.
+	EA::Thread::SetAssertionFailureFunction(EAThreadFailure, NULL);
+
+	#if defined(EA_DLL) && defined(EA_MEMORY_ENABLED) && EA_MEMORY_ENABLED
+		EA::Allocator::InitSharedDllAllocator();
+	#endif
+
+	//Set EAThread to use the Default Allocator to keep track of our memory usage
+	EA::Thread::SetAllocator(EA::Allocator::ICoreAllocator::GetDefaultAllocator());
+	EA::Thread::InitCallstack();
+
+	// Print ThreadId for this primary thread.
+	const ThreadId threadId = GetThreadId();
+	ReportVerbosity(1, "Primary thread ThreadId: %s\n", EAThreadThreadIdToString(threadId));
+
+	// Print SysThreadId for this primary thread.
+	const SysThreadId sysThreadId = GetSysThreadId(threadId);
+	ReportVerbosity(1, "Primary thread SysThreadId: %s\n", EAThreadSysThreadIdToString(sysThreadId));
+
+	// Print thread priority for this primary thread.
+	const int nPriority = EA::Thread::GetThreadPriority();
+	ReportVerbosity(1, "Primary thread priority: %d\n", nPriority);
+
+	const int nProcessorCount = EA::Thread::GetProcessorCount();
+	ReportVerbosity(1, "Currently active virtual processor count: %d\n", nProcessorCount);
+
+	#if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE && !EA_USE_CPP11_CONCURRENCY
+		const DWORD dwCurrentThreadId = GetCurrentThreadId(); // This is a system OS call.
+		EATEST_VERIFY_F(sysThreadId == dwCurrentThreadId, "GetSysThreadId failed. SysThreadId = %u, sys thread id = %u.\n", (unsigned)sysThreadId, (unsigned)dwCurrentThreadId);
+	#endif
+
+	// Add the tests
+	TestApplication testSuite("EAThread Unit Tests", argc, argv);
+
+
+
+	testSuite.AddTest("Atomic",            TestThreadAtomic);
+	testSuite.AddTest("Barrier",           TestThreadBarrier);
+	testSuite.AddTest("Callstack",         TestThreadCallstack);
+	testSuite.AddTest("Condition",         TestThreadCondition);
+	testSuite.AddTest("EnumerateThreads",  TestEnumerateThreads);
+	testSuite.AddTest("Futex",             TestThreadFutex);
+	testSuite.AddTest("Misc",              TestThreadMisc);
+	testSuite.AddTest("Mutex",             TestThreadMutex);
+	testSuite.AddTest("RWMutex",           TestThreadRWMutex);
+	testSuite.AddTest("RWSemaphore",       TestThreadRWSemaLock);
+	testSuite.AddTest("RWSpinLock",        TestThreadRWSpinLock);
+	testSuite.AddTest("Semaphore",         TestThreadSemaphore);
+	testSuite.AddTest("SmartPtr",          TestThreadSmartPtr);
+	testSuite.AddTest("SpinLock",          TestThreadSpinLock);
+	testSuite.AddTest("Storage",           TestThreadStorage);
+	testSuite.AddTest("Sync",              TestThreadSync);
+	testSuite.AddTest("Thread",            TestThreadThread);
+	testSuite.AddTest("ThreadPool",        TestThreadThreadPool);
+
+	nErrorCount += testSuite.Run();
+
+#ifndef EA_USE_CPP11_CONCURRENCY
+	// Verify the converted a EA::Thread::ThreadId to a EA::Thread::SysThreadId id matches this thread context id.
+	const ThreadId convertedThreadId = GetThreadId(sysThreadId);
+	EATEST_VERIFY_F(threadId == convertedThreadId , "GetThreadId failed to convert SysThreadId. ThreadId = %s, converted thread id = %s.\n", EAThreadThreadIdToString(threadId),  EAThreadThreadIdToString(convertedThreadId));
+#endif
+
+	EA::Thread::ShutdownCallstack();
+	EA::EAMain::PlatformShutdown(nErrorCount);
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+

+ 46 - 0
test/thread/source/TestThread.h

@@ -0,0 +1,46 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+
+#ifndef TESTTHREAD_H
+#define TESTTHREAD_H
+
+
+extern unsigned int gTestLengthSeconds;
+extern bool IsSuperUser();
+
+// The maximum number of threads spawned during EAThread unit tests.
+#ifndef EATHREAD_MAX_CONCURRENT_THREAD_COUNT
+	#if defined(EA_PLATFORM_DESKTOP)
+		#define EATHREAD_MAX_CONCURRENT_THREAD_COUNT 16
+	#elif defined(EA_PLATFORM_MOBILE)
+		#define EATHREAD_MAX_CONCURRENT_THREAD_COUNT 4
+	#else
+		#define EATHREAD_MAX_CONCURRENT_THREAD_COUNT 8
+	#endif
+#endif
+
+
+int TestThreadSync();
+int TestThreadAtomic();
+int TestThreadCallstack();
+int TestThreadStorage();
+int TestThreadSpinLock();
+int TestThreadRWSpinLock();
+int TestThreadFutex();
+int TestThreadMutex();
+int TestThreadRWMutex();
+int TestThreadSemaphore();
+int TestThreadRWSemaLock();
+int TestThreadCondition();
+int TestThreadBarrier();
+int TestThreadThread();
+int TestThreadThreadPool();
+int TestThreadSmartPtr();
+int TestThreadMisc();
+int TestEnumerateThreads();
+
+#endif // Header include guard
+
+

+ 1007 - 0
test/thread/source/TestThreadAtomic.cpp

@@ -0,0 +1,1007 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_atomic.h>
+#include <eathread/eathread_thread.h>
+
+EA_DISABLE_VC_WARNING(4265 4365 4836 4571 4625 4626 4628 4193 4127 4548)
+#include <string.h>
+EA_RESTORE_VC_WARNING()
+
+#if defined(_MSC_VER)
+	#pragma warning(disable: 4996) // This function or variable may be unsafe / deprecated.
+#endif
+
+#include <EASTL/numeric_limits.h>
+
+
+using namespace EA::Thread;
+
+
+#if EA_THREADS_AVAILABLE
+
+const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+struct AWorkData32
+{
+	volatile bool mbShouldQuit;
+	AtomicInt32   mnAtomicInteger1;
+	AtomicInt32   mnAtomicInteger2;
+	AtomicInt32   mnErrorCount;
+
+	AWorkData32() : mbShouldQuit(false),
+					mnAtomicInteger1(0), mnAtomicInteger2(0), 
+					mnErrorCount(0) {}
+};
+
+
+
+static intptr_t Atomic32TestThreadFunction1(void* pvWorkData)
+{
+	int            nErrorCount = 0;
+	AWorkData32*   pWorkData   = (AWorkData32*)pvWorkData;
+	const ThreadId threadId    = GetThreadId();
+
+	EA::UnitTest::ReportVerbosity(1, "Atomic test function 1 created, thread id %s\n", EAThreadThreadIdToString(threadId));
+
+	// Do a series of operations, the final result of which is zero.
+	while(!pWorkData->mbShouldQuit)
+	{
+		++pWorkData->mnAtomicInteger1;
+		++pWorkData->mnAtomicInteger2;
+		--pWorkData->mnAtomicInteger1;
+		--pWorkData->mnAtomicInteger2;
+		pWorkData->mnAtomicInteger1 += 5;
+		pWorkData->mnAtomicInteger2 += 5;
+		pWorkData->mnAtomicInteger1 -= 5;
+		pWorkData->mnAtomicInteger2 -= 5;
+		pWorkData->mnAtomicInteger1++;
+		pWorkData->mnAtomicInteger2++;
+		pWorkData->mnAtomicInteger1--;
+		pWorkData->mnAtomicInteger2--;
+		ThreadCooperativeYield();
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	EA::UnitTest::ReportVerbosity(1, "Atomic test function 1 exiting, thread id %s\n", EAThreadThreadIdToString(threadId));
+	return 0;
+}
+
+
+static intptr_t Atomic32TestThreadFunction2(void* pvWorkData)
+{
+	int            nErrorCount = 0;
+	AWorkData32*   pWorkData   = (AWorkData32*)pvWorkData;
+	const ThreadId threadId = GetThreadId();
+	ThreadUniqueId threadUniqueId;
+	EAThreadGetUniqueId(threadUniqueId);
+	int32_t        threadUniqueId32 = (int32_t)threadUniqueId;
+
+	EA::UnitTest::ReportVerbosity(1, "Atomic test function 2 created, thread id %s\n", EAThreadThreadIdToString(threadId));
+
+	// Test the SetValueConditional function. We basically create a spinlock here.
+	while(!pWorkData->mbShouldQuit)
+	{
+		if(pWorkData->mnAtomicInteger1.SetValueConditional(threadUniqueId32, 0x11223344))
+		{
+			EATEST_VERIFY_MSG(pWorkData->mnAtomicInteger1 == threadUniqueId32, "AtomicInt SetValueConditional failure.");
+			pWorkData->mnAtomicInteger1 = 0x11223344;
+		}
+
+		ThreadCooperativeYield();
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	EA::UnitTest::ReportVerbosity(1, "Atomic test function 2 exiting, thread id %s\n", EAThreadThreadIdToString(threadId));
+	return 0;
+}
+
+
+struct AWorkData64
+{
+	volatile bool mbShouldQuit;
+	AtomicInt64   mnAtomicInteger1;
+	AtomicInt64   mnAtomicInteger2;
+	AtomicInt64   mnErrorCount;
+
+	AWorkData64() : mbShouldQuit(false),
+					mnAtomicInteger1(0), mnAtomicInteger2(0), 
+					mnErrorCount(0) {}
+};
+
+
+static intptr_t Atomic64TestThreadFunction1(void* pvWorkData)
+{
+	int            nErrorCount = 0;
+	AWorkData64*   pWorkData   = (AWorkData64*)pvWorkData;
+	const ThreadId threadId    = GetThreadId();
+
+	EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 1 created, thread id %s\n", EAThreadThreadIdToString(threadId));
+
+	// Do a series of operations, the final result of which is zero.
+	while(!pWorkData->mbShouldQuit)
+	{
+		++pWorkData->mnAtomicInteger1;
+		++pWorkData->mnAtomicInteger2;
+		--pWorkData->mnAtomicInteger1;
+		--pWorkData->mnAtomicInteger2;
+		pWorkData->mnAtomicInteger1 += UINT64_C(0x0000000fffffffff);
+		pWorkData->mnAtomicInteger2 += UINT64_C(0x0000000ffffffffe);
+		pWorkData->mnAtomicInteger1 -= UINT64_C(0x0000000fffffffff);
+		pWorkData->mnAtomicInteger2 -= UINT64_C(0x0000000ffffffffe);
+		pWorkData->mnAtomicInteger1++;
+		pWorkData->mnAtomicInteger2++;
+		pWorkData->mnAtomicInteger1--;
+		pWorkData->mnAtomicInteger2--;
+		ThreadCooperativeYield();
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 1 exiting, thread id %s\n", EAThreadThreadIdToString(threadId));
+	return 0;
+}
+
+
+static intptr_t Atomic64TestThreadFunction2(void* pvWorkData)
+{
+	int            nErrorCount = 0;
+	AWorkData64*   pWorkData   = (AWorkData64*)pvWorkData;
+	const ThreadId threadId = GetThreadId();
+	ThreadUniqueId threadUniqueId;
+	EAThreadGetUniqueId(threadUniqueId);
+	uint64_t       threadUnqueId64 = (uint64_t)threadUniqueId | UINT64_C(0xeeeeddddffffffff);
+
+	EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 2 created, thread id %s\n", EAThreadThreadIdToString(threadId));
+
+	// Test the SetValueConditional function. We basically create a spinlock here.
+	while(!pWorkData->mbShouldQuit)
+	{
+		if(pWorkData->mnAtomicInteger1.SetValueConditional(threadUnqueId64, 0x1122334455667788))
+		{
+			EATEST_VERIFY_MSG(pWorkData->mnAtomicInteger1 == static_cast<int64_t>(threadUnqueId64), "AtomicInt64 SetValueConditional failure.");
+			pWorkData->mnAtomicInteger1.SetValue(0x1122334455667788);
+		}
+
+		ThreadCooperativeYield();
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 2 exiting, thread id %s\n", EAThreadThreadIdToString(threadId));
+	return 0;
+}
+
+
+static intptr_t Atomic64TestThreadFunction3(void* pvWorkData)
+{
+	int            nErrorCount = 0;
+	AWorkData64*   pWorkData   = (AWorkData64*)pvWorkData;
+	const ThreadId threadId    = GetThreadId();
+	const uint64_t value0      = UINT64_C(0x0000000000000000);
+	const uint64_t value1      = UINT64_C(0xffffffffffffffff);
+	
+	EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 2 created, thread id %s\n", EAThreadThreadIdToString(threadId));
+
+	// Test the SetValueConditional function.
+	while(!pWorkData->mbShouldQuit)
+	{
+		pWorkData->mnAtomicInteger1.SetValueConditional(value0, value1);
+		uint64_t currentValue = pWorkData->mnAtomicInteger1.GetValue();
+		EATEST_VERIFY_MSG((currentValue == value0) || (currentValue == value1), "AtomicInt64 SetValueConditional failure.");
+		
+		pWorkData->mnAtomicInteger1.SetValueConditional(value1, value0);
+		currentValue = pWorkData->mnAtomicInteger1.GetValue();
+		EATEST_VERIFY_MSG((currentValue == value0) || (currentValue == value1), "AtomicInt64 SetValueConditional failure.");
+
+		ThreadCooperativeYield();
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	EA::UnitTest::ReportVerbosity(1, "Atomic64 test function 2 exiting, thread id %s\n", EAThreadThreadIdToString(threadId));
+	return 0;
+}
+
+template<typename T>
+int TestSimpleAtomicOps()
+{
+	int nErrorCount = 0; 
+	bool result = false;
+
+	alignas(16) T value = 0;
+	alignas(16) T dest = 0;
+	alignas(16) T conditionFail = 4;
+	alignas(16) T conditionSucceed = 0;
+
+	// AtomicGetValue
+	dest = 3;
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 3, "AtomicGetValue failure\n");
+
+	// AtomicSetValue
+	value = AtomicSetValue(&dest, 4);
+	EATEST_VERIFY_MSG(value == 3, "AtomicSetValue failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 4, "AtomicSetValue failure\n");
+
+	// AtomicFetchIncrement
+	value = AtomicFetchIncrement(&dest);
+	EATEST_VERIFY_MSG(value == 4, "AtomicFetchIncrement failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 5, "AtomicFetchIncrement failure\n");
+
+	// AtomicFetchDecrement
+	value = AtomicFetchDecrement(&dest);
+	EATEST_VERIFY_MSG(value == 5, "AtomicFetchDecrement failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 4, "AtomicFetchDecrement failure\n");
+
+	// AtomicFetchAdd
+	value = AtomicFetchAdd(&dest, 3);
+	EATEST_VERIFY_MSG(value == 4, "AtomicFetchAdd failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 7, "AtomicFetchAdd failure\n");
+
+	// AtomicFetchSub
+	value = AtomicFetchSub(&dest, 3);
+	EATEST_VERIFY_MSG(value == 7, "AtomicFetchSub failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 4, "AtomicFetchSub failure\n");
+	value = AtomicFetchSub(&dest, T(-3));
+	EATEST_VERIFY_MSG(value == 4, "AtomicFetchSub failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 7, "AtomicFetchSub failure\n");
+
+	// AtomicFetchOr
+	value = AtomicFetchOr(&dest, 8);
+	EATEST_VERIFY_MSG(value == 7, "AtomicFetchOr failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 15, "AtomicFetchOr failure\n");
+
+	// AtomicFetchAnd
+	value = AtomicFetchAnd(&dest, 3);
+	EATEST_VERIFY_MSG(value == 15, "AtomicFetchAnd failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 3, "AtomicFetchAnd failure\n");
+
+	// AtomicFetchXor
+	value = AtomicFetchXor(&dest, dest);
+	EATEST_VERIFY_MSG(value == 3, "AtomicFetchXor failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 0, "AtomicFetchXor failure\n");
+
+	// AtomicFetchSwap
+	value = AtomicFetchSwap(&dest, 5);
+	EATEST_VERIFY_MSG(value == 0, "AtomicFetchSwap failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 5, "AtomicFetchSwap failure\n");
+
+	// AtomicSetValueConditional
+	dest             = 0;
+	value            = 1;
+	conditionFail    = 4;
+	conditionSucceed = 0;
+
+	// Try to do conditional fetch swap which should fail
+	value = EA::Thread::AtomicFetchSwapConditional(&dest, 1, conditionFail);
+	EATEST_VERIFY_MSG(value != conditionFail, "AtomicFetchSwapConditional failure 0\n");
+	EATEST_VERIFY_MSG(dest == 0, "AtomicFetchSwapConditional failure 1\n");
+
+	// Try to do conditional fetch swap which should succeed
+	value = EA::Thread::AtomicFetchSwapConditional(&dest, 1, conditionSucceed);
+	EATEST_VERIFY_MSG(value == conditionSucceed, "AtomicFetchSwapConditional failure 2\n");
+	EATEST_VERIFY_MSG(dest == 1, "AtomicFetchSwapConditional failure 3\n");
+
+	// reset before the next test
+	dest = 0;
+	value = 1;
+
+	// Try to do an update which should fail.
+	result = EA::Thread::AtomicSetValueConditional(&dest, value, conditionFail);
+	EATEST_VERIFY_MSG(!result, "AtomicSetValueConditional failure 0\n");
+	EATEST_VERIFY_MSG(dest == 0, "AtomicSetValueConditional failure 1\n");
+
+	// Try to do an update which should succeed.
+	result = EA::Thread::AtomicSetValueConditional(&dest, value, conditionSucceed);
+	EATEST_VERIFY_MSG(result, "AtomicSetValueConditional failure 2\n");
+	EATEST_VERIFY_MSG(dest == 1, "AtomicSetValueConditional failure 3\n");
+
+	return nErrorCount;
+}
+
+template<typename T>
+inline int TestAtomicsSizeBoundaries()
+{
+	static_assert(eastl::is_floating_point<T>::value == false, "atomic floats not supported");
+
+	int nErrorCount = 0;
+	bool result = false;
+	alignas(16) T value = 0, dest = 0;
+
+	T max = eastl::numeric_limits<T>::max();
+	T lowest = eastl::numeric_limits<T>::lowest();
+
+
+	/// Test the max boundary 
+	///
+	value = AtomicSetValue(&dest, max);
+	EATEST_VERIFY_MSG(value == 0, "max failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == max, "max failure\n");
+
+	value = AtomicFetchIncrement(&dest);
+	EATEST_VERIFY_MSG(value == max, "max failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == lowest, "max failure\n");
+
+	value = AtomicFetchDecrement(&dest);
+	EATEST_VERIFY_MSG(value == lowest, "max failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == max, "max failure\n");
+
+	value = AtomicFetchAdd(&dest, 1);
+	EATEST_VERIFY_MSG(value == max, "max failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == lowest, "max failure\n");
+
+	value = AtomicFetchAnd(&dest, lowest);
+	EATEST_VERIFY_MSG(value == lowest, "max failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == lowest, "max failure\n");
+
+	value = AtomicFetchXor(&dest, lowest);
+	EATEST_VERIFY_MSG(value == lowest, "max failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 0, "max failure\n");
+
+	value = AtomicFetchSwap(&dest, lowest);
+	EATEST_VERIFY_MSG(value == 0, "max failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == lowest, "max failure\n");
+
+	// reset to zero
+	result = AtomicSetValueConditional(&dest, 0, lowest);
+	EATEST_VERIFY_MSG(result, "max failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == 0, "max failure\n");
+	
+
+
+	/// Test the lowest boundary 
+	///
+	value = AtomicSetValue(&dest, lowest);
+	EATEST_VERIFY_MSG(value == 0, "lowest failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == lowest, "lowest failure\n");
+
+	// decrement the lowest to ensure we rollover to the highest value
+	value = AtomicFetchDecrement(&dest);
+	EATEST_VERIFY_MSG(value == lowest, "lowest failure\n");
+	value = AtomicGetValue(&dest);
+	EATEST_VERIFY_MSG(value == max, "lowest failure\n");
+
+
+	return nErrorCount;
+}
+
+template<typename T>
+inline int TestAtomicsConstFetch()
+{
+	int nErrorCount = 0;
+
+	{
+		alignas(16) const T value = 13;
+		auto r = AtomicGetValue(&value);
+		EATEST_VERIFY_MSG(r == value, "failure\n");
+	}
+
+	{
+		struct Foo
+		{
+			Foo(uint32_t n) : baz(n) {}
+			uint32_t getBaz() const { return AtomicGetValue(&this->baz); }
+			uint32_t baz;
+		};
+
+		Foo foo(42);
+		auto r = foo.getBaz();
+		EATEST_VERIFY_MSG(r == 42, "failure\n");
+	}
+
+	return nErrorCount;
+}
+
+template<typename T>
+int TestAtomicIntT()
+{
+	int nErrorCount = 0;
+
+	AtomicInt<T> atomicInt = 0;
+
+	++atomicInt;
+	--atomicInt;
+	atomicInt += 5;
+	atomicInt -= 5;
+	atomicInt++;
+	atomicInt--;
+
+	EATEST_VERIFY(atomicInt == 0);
+
+	return nErrorCount;
+}
+
+template<typename T>
+int TestNonMemberAtomics()
+{
+	int nErrorCount = 0;
+	nErrorCount += TestSimpleAtomicOps<T>();
+	nErrorCount += TestAtomicsSizeBoundaries<T>();
+	nErrorCount += TestAtomicsConstFetch<T>();
+	return nErrorCount;
+}
+
+#endif // #if EA_THREADS_AVAILABLE
+
+
+int TestThreadAtomic()
+{
+	int nErrorCount(0);
+
+	{ // Initial tests of 128 bit atomics
+		#if EATHREAD_ATOMIC_128_SUPPORTED // This will be true only for 64+ bit platforms.
+
+			// To consider: Use __int128_t on GCC for GCC >= 4.1
+
+			EA_ALIGN(128) int64_t dest128[2]             = { 0, 0 };
+			EA_ALIGN(128) int64_t value128[2]            = { 1, 2 };
+			EA_ALIGN(128) int64_t condition128Fail[2]    = { 4, 5 };
+			EA_ALIGN(128) int64_t condition128Succeed[2] = { 0, 0 };
+			bool    result;
+
+			// Try to do an update which should fail.
+			result = EA::Thread::AtomicSetValueConditionall28(dest128, value128, condition128Fail);
+
+			EATEST_VERIFY_MSG(!result, "AtomicSetValueConditional failure: result should have been false.\n");
+			EATEST_VERIFY_F((dest128[0] == 0) && (dest128[1] == 0), "AtomicSetValueConditional failure: dest128[0]:%I64d dest128[1]:%I64d\n", dest128[0], dest128[1]);
+			EATEST_VERIFY_F((value128[0] == 1) && (value128[1] == 2), "AtomicSetValueConditional failure: value128[0]:%I64d value128[1]:%I64d\n", value128[0], value128[1]);
+			EATEST_VERIFY_F((condition128Fail[0] == 4) && (condition128Fail[1] == 5), "AtomicSetValueConditional failure: condition128Fail[0]:%I64d condition128Fail[1]:%I64d\n", condition128Fail[0], condition128Fail[1]);
+			EATEST_VERIFY_F((condition128Succeed[0] == 0) && (condition128Succeed[1] == 0), "AtomicSetValueConditional failure: condition128Succeed[0]:%I64d condition128Succeed[1]:%I64d\n", condition128Succeed[0], condition128Succeed[1]);
+
+			// Try to do an update which should succeed.
+			// VC++ for VS2010 misgenerates the atomic code below in optimized builds, by passing what appears to be the wrong value for dest128 to AtomicSetValueConditional128. 
+			// We added some diagnostic code and now the compiler does the right thing. I (Paul Pedriana) wonder if the problem is related to 
+			// the alignment specification for dest, which must be 128 byte aligned for AtomicSetValueConditional128 (cmpxchg16b) to work.
+			// The dest address is indeed being aligned to 16 bytes, so that's not the problem.
+			EA::UnitTest::ReportVerbosity(1, "%p %p %p\n", dest128, value128, condition128Succeed);
+			result = EA::Thread::AtomicSetValueConditionall28(dest128, value128, condition128Succeed);
+
+			EATEST_VERIFY_MSG(result, "AtomicSetValueConditional failure: result should have been true.\n");
+			EATEST_VERIFY_F((dest128[0] == 1) && (dest128[1] == 2), "AtomicSetValueConditional failure: dest128:%p dest128[0]:%I64d dest128[1]:%I64d\n", dest128, dest128[0], dest128[1]);
+			EATEST_VERIFY_F((value128[0] == 1) && (value128[1] == 2), "AtomicSetValueConditional failure: value128:%p value128[0]:%I64d value128[1]:%I64d\n", value128, value128[0], value128[1]);
+			EATEST_VERIFY_F((condition128Fail[0] == 4) && (condition128Fail[1] == 5), "AtomicSetValueConditional failure: condition128Fail:%p condition128Fail[0]:%I64d condition128Fail[1]:%I64d\n", condition128Fail, condition128Fail[0], condition128Fail[1]);
+			EATEST_VERIFY_F((condition128Succeed[0] == 0) && (condition128Succeed[1] == 0), "AtomicSetValueConditional failure: condition128Succeed:%p condition128Succeed[0]:%I64d condition128Succeed[1]:%I64d\n", condition128Succeed, condition128Succeed[0], condition128Succeed[1]);
+
+
+			#if defined(EA_COMPILER_GNUC)   // GCC defines __int128_t as a built-in type.
+
+				__int128_t dest;
+				__int128_t value;
+				__int128_t conditionFail;
+				__int128_t conditionSucceed;
+
+				// AtomicGetValue
+				dest = 3;
+				value = AtomicGetValue(&dest);
+				EATEST_VERIFY_MSG(value == 3, "AtomicGetValue[128] failure\n");
+
+				// AtomicSetValue
+				AtomicSetValue(&dest, 4);
+				value = AtomicGetValue(&dest);
+				EATEST_VERIFY_MSG(value == 4, "AtomicSetValue[128] failure\n");
+
+				// AtomicIncrement
+				value = AtomicIncrement(&dest);
+				EATEST_VERIFY_MSG(value == 5, "AtomicIncrement[128] failure\n");
+				value = AtomicGetValue(&dest);
+				EATEST_VERIFY_MSG(value == 5, "AtomicIncrement[128] failure\n");
+
+				// AtomicDecrement
+				value = AtomicDecrement(&dest);
+				EATEST_VERIFY_MSG(value == 4, "AtomicDecrement[128] failure\n");
+				value = AtomicGetValue(&dest);
+				EATEST_VERIFY_MSG(value == 4, "AtomicDecrement[128] failure\n");
+
+				// AtomicAdd
+				value = AtomicAdd(&dest, 3);
+				EATEST_VERIFY_MSG(value == 7, "AtomicAdd[128] failure\n");
+				value = AtomicGetValue(&dest);
+				EATEST_VERIFY_MSG(value == 7, "AtomicAdd[128] failure\n");
+
+				// AtomicOr
+				value = AtomicOr(&dest, 8);
+				EATEST_VERIFY_MSG(value == 15, "AtomicOr[128] failure\n");
+				value = AtomicGetValue(&dest);
+				EATEST_VERIFY_MSG(value == 15, "AtomicOr[128] failure\n");
+
+				// AtomicAnd
+				value = AtomicAnd(&dest, 3);
+				EATEST_VERIFY_MSG(value == 3, "AtomicAnd[128] failure\n");
+				value = AtomicGetValue(&dest);
+				EATEST_VERIFY_MSG(value == 3, "AtomicAnd[128] failure\n");
+
+				// AtomicXor
+				value = AtomicXor(&dest, dest);
+				EATEST_VERIFY_MSG(value == 0, "AtomicXor[128] failure\n");
+				value = AtomicGetValue(&dest);
+				EATEST_VERIFY_MSG(value == 0, "AtomicXor[128] failure\n");
+
+				// AtomicSwap
+				value = AtomicSwap(&dest, 5);
+				EATEST_VERIFY_MSG(value == 0, "AtomicSwap[128] failure\n");
+				value = AtomicGetValue(&dest);
+				EATEST_VERIFY_MSG(value == 5, "AtomicSwap[128] failure\n");
+
+				// AtomicSetValueConditional
+				dest             = 0;
+				value            = 1;
+				conditionFail    = 4;
+				conditionSucceed = 0;
+
+				// Try to do an update which should fail.
+				result = EA::Thread::AtomicSetValueConditional(&dest, value, conditionFail);
+
+				EATEST_VERIFY_MSG(!result, "AtomicSetValueConditional failure 0\n");
+				EATEST_VERIFY_MSG(dest == 0, "AtomicSetValueConditional failure 1\n");
+
+				// Try to do an update which should succeed.
+				result = EA::Thread::AtomicSetValueConditional(&dest, value, conditionSucceed);
+
+				EATEST_VERIFY_MSG(result, "AtomicSetValueConditional failure 2\n");
+				EATEST_VERIFY_MSG(dest == 1, "AtomicSetValueConditional failure 3\n");
+
+				if(nErrorCount != 0)
+					return nErrorCount;
+
+			#endif
+
+		#endif
+	}
+
+	{  // Basic single-threaded Atomic test.
+		AtomicInt32  i32(1);
+		AtomicUint32 u32(1);
+
+		EATEST_VERIFY_MSG(i32.GetValue() == 1, "AtomicInt32 failure.");
+		EATEST_VERIFY_MSG(u32.GetValue() == 1, "AtomicUint32 failure.");
+
+		char buffer[64];
+		sprintf(buffer, "%d %u", (signed int)i32.GetValue(), (unsigned int)u32.GetValue());
+		EATEST_VERIFY_MSG(strcmp(buffer, "1 1") == 0, "AtomicInt32 failure.");
+
+		// Copy ctor/operator=.
+		AtomicInt32 i32CopyA(i32);
+		AtomicInt32 i32CopyB(i32CopyA);
+		i32CopyA = i32CopyB;
+
+		sprintf(buffer, "%d %d", (signed int)i32CopyA.GetValue(), (signed int)i32CopyB.GetValue());
+		EATEST_VERIFY_MSG(strcmp(buffer, "1 1") == 0, "AtomicInt32 failure.");
+
+		// Test platforms that support 64 bits..
+		AtomicInt64  i64(1);
+		AtomicUint64 u64(1);
+
+		sprintf(buffer, "%.0f %.0f", (double)i64.GetValue(), (double)u64.GetValue());
+		EATEST_VERIFY_MSG(strcmp(buffer, "1 1") == 0, "AtomicInt64 failure.");
+
+		// Copy ctor/operator=.
+		AtomicInt64 i64CopyA(i64);
+		AtomicInt64 i64CopyB(i64CopyA);
+		i64CopyA = i64CopyB;
+
+		sprintf(buffer, "%d %d", (signed int)i64CopyA.GetValue(), (signed int)i64CopyB.GetValue());
+		EATEST_VERIFY_MSG(strcmp(buffer, "1 1") == 0, "AtomicInt64 failure.");
+	
+		bool result = i64.SetValueConditional(2, 99999); // This should not set the value to 2.
+		EATEST_VERIFY_MSG(!result && (i64.GetValue() == 1), "AtomicInt64 failure.");
+	
+		i64.SetValueConditional(2, 1);     // This should set the value to 2.
+		EATEST_VERIFY_MSG(!result && (i64.GetValue() == 2), "AtomicInt64 failure.");
+	}
+
+	{  // Basic single-threaded AtomicInt32 test.
+		AtomicInt32 i(0); // Note that this assignment goes through AtomicInt32 operator=().
+		AtomicInt32::ValueType x;
+
+		EATEST_VERIFY_MSG(i == 0, "AtomicInt32 failure.");
+
+		++i;
+		i++;
+		--i;
+		i--;
+		i += 7;
+		i -= 3;
+		EATEST_VERIFY_MSG(i == 4, "AtomicInt32 failure.");
+
+		i = 2;
+		x = i.GetValue();
+		EATEST_VERIFY_MSG(x == 2, "AtomicInt32 failure.");
+
+		i.Increment();
+		i.Decrement();
+		i.Add(5);
+		i.Add(-2);
+		EATEST_VERIFY_MSG(i == 5, "AtomicInt32 failure.");
+
+		i.SetValue(6);
+		EATEST_VERIFY_MSG(i == 6, "AtomicInt32 failure.");
+
+		bool bWasEqualTo10000 = i.SetValueConditional(3, 10000);
+		EATEST_VERIFY_MSG(!bWasEqualTo10000, "AtomicInt32 failure.");
+
+		bool bWasEqualTo6 = i.SetValueConditional(3, 6);
+		EATEST_VERIFY_MSG(bWasEqualTo6, "AtomicInt32 failure.");
+	}
+
+	{  // Verify pre-increment/post-increment works as intended.
+		AtomicInt32            i32(0);
+		AtomicInt32::ValueType x32;
+
+		// ValueType SetValue(ValueType n)
+		// Safely sets a new value. Returns the old value.
+		x32 = i32.SetValue(1);
+		EATEST_VERIFY_MSG(x32 == 0, "AtomicInt return value failure.");
+
+		// ValueType Increment()
+		// Safely increments the value. Returns the new value.
+		x32 = i32.Increment();
+		EATEST_VERIFY_MSG(x32 == 2, "AtomicInt return value failure.");
+
+		// ValueType Decrement()
+		// Safely decrements the value. Returns the new value.
+		x32 = i32.Decrement();
+		EATEST_VERIFY_MSG(x32 == 1, "AtomicInt return value failure.");
+
+		// ValueType Add(ValueType n)
+		// Safely adds a value, which can be negative. Returns the new value.
+		x32 = i32.Add(35);
+		EATEST_VERIFY_MSG(x32 == 36, "AtomicInt return value failure.");
+
+		// ValueType operator=(ValueType n)
+		// Safely assigns the value. Returns the new value.
+		x32 = (i32 = 17);
+		EATEST_VERIFY_MSG(x32 == 17, "AtomicInt return value failure.");
+
+		// ValueType operator+=(ValueType n)
+		// Safely adds a value, which can be negative. Returns the new value.
+		x32 = (i32 += 3);
+		EATEST_VERIFY_MSG(x32 == 20, "AtomicInt return value failure.");
+
+		// ValueType operator-=(ValueType n)
+		// Safely subtracts a value, which can be negative. Returns the new value.
+		x32 = (i32 -= 6);
+		EATEST_VERIFY_MSG(x32 == 14, "AtomicInt return value failure.");
+
+		// ValueType operator++()
+		// pre-increment operator++
+		x32 = ++i32;
+		EATEST_VERIFY_MSG(x32 == 15, "AtomicInt return value failure.");
+		EATEST_VERIFY_MSG(i32 == 15, "AtomicInt return value failure.");
+
+		// ValueType operator++(int)
+		// post-increment operator++
+		x32 = i32++;
+		EATEST_VERIFY_MSG(x32 == 15, "AtomicInt return value failure.");
+		EATEST_VERIFY_MSG(i32 == 16, "AtomicInt return value failure.");
+
+		// ValueType operator--()
+		// pre-increment operator--
+		x32 = --i32;
+		EATEST_VERIFY_MSG(x32 == 15, "AtomicInt return value failure.");
+		EATEST_VERIFY_MSG(i32 == 15, "AtomicInt return value failure.");
+
+		// ValueType operator--(int)
+		// post-increment operator--
+		x32 = i32--;
+		EATEST_VERIFY_MSG(x32 == 15, "AtomicInt return value failure.");
+		EATEST_VERIFY_MSG(i32 == 14, "AtomicInt return value failure.");
+	}
+
+
+	{  // Verify pre-increment/post-increment works as intended.
+		AtomicInt64            i64(0);
+		AtomicInt64::ValueType x64;
+
+		// ValueType SetValue(ValueType n)
+		// Safely sets a new value. Returns the old value.
+		x64 = i64.SetValue(1);
+		EATEST_VERIFY_MSG(x64 == 0, "AtomicInt return value failure.");
+
+		// ValueType Increment()
+		// Safely increments the value. Returns the new value.
+		x64 = i64.Increment();
+		EATEST_VERIFY_MSG(x64 == 2, "AtomicInt return value failure.");
+
+		// ValueType Decrement()
+		// Safely decrements the value. Returns the new value.
+		x64 = i64.Decrement();
+		EATEST_VERIFY_MSG(x64 == 1, "AtomicInt return value failure.");
+
+		// ValueType Add(ValueType n)
+		// Safely adds a value, which can be negative. Returns the new value.
+		x64 = i64.Add(35);
+		EATEST_VERIFY_MSG(x64 == 36, "AtomicInt return value failure.");
+
+		// ValueType operator=(ValueType n)
+		// Safely assigns the value. Returns the new value.
+		x64 = (i64 = 17);
+		EATEST_VERIFY_MSG(x64 == 17, "AtomicInt return value failure.");
+
+		// ValueType operator+=(ValueType n)
+		// Safely adds a value, which can be negative. Returns the new value.
+		x64 = (i64 += 3);
+		EATEST_VERIFY_MSG(x64 == 20, "AtomicInt return value failure.");
+
+		// ValueType operator-=(ValueType n)
+		// Safely subtracts a value, which can be negative. Returns the new value.
+		x64 = (i64 -= 6);
+		EATEST_VERIFY_MSG(x64 == 14, "AtomicInt return value failure.");
+
+		// ValueType operator++()
+		// pre-increment operator++
+		x64 = ++i64;
+		EATEST_VERIFY_MSG(x64 == 15, "AtomicInt return value failure.");
+		EATEST_VERIFY_MSG(i64 == 15, "AtomicInt return value failure.");
+
+		// ValueType operator++(int)
+		// post-increment operator++
+		x64 = i64++;
+		EATEST_VERIFY_MSG(x64 == 15, "AtomicInt return value failure.");
+		EATEST_VERIFY_MSG(i64 == 16, "AtomicInt return value failure.");
+
+		// ValueType operator--()
+		// pre-increment operator--
+		x64 = --i64;
+		EATEST_VERIFY_MSG(x64 == 15, "AtomicInt return value failure.");
+		EATEST_VERIFY_MSG(i64 == 15, "AtomicInt return value failure.");
+
+		// ValueType operator--(int)
+		// post-increment operator--
+		x64 = i64--;
+		EATEST_VERIFY_MSG(x64 == 15, "AtomicInt return value failure.");
+		EATEST_VERIFY_MSG(i64 == 14, "AtomicInt return value failure.");
+	}
+
+	{ // Basic single-threaded AtomicPointer test.
+		AtomicPointer p(NULL);
+		AtomicPointer::PointerValueType pTemp;
+
+		EATEST_VERIFY_MSG(p.GetValue() == NULL, "AtomicPointer failure.");
+
+		++p;
+		p++;
+		--p;
+		p--;
+		p += 7;
+		p -= 3;
+		EATEST_VERIFY_MSG(p == (void*)4, "AtomicPointer failure.");
+
+		p = (void*)2;
+		pTemp = p.GetValue();
+		EATEST_VERIFY_MSG((uintptr_t)pTemp == 2, "AtomicPointer failure.");
+
+		p.Increment();
+		p.Decrement();
+		p.Add(5);
+		p.Add(-2);
+		EATEST_VERIFY_MSG(p == (void*)5, "AtomicPointer failure.");
+
+		p.SetValue((void*)6);
+		EATEST_VERIFY_MSG(p == (void*)6, "AtomicPointer failure.");
+
+		bool bWasEqualTo10000 = p.SetValueConditional((void*)3, (void*)10000);
+		EATEST_VERIFY_MSG(!bWasEqualTo10000, "AtomicPointer failure.");
+
+		bool bWasEqualTo6 = p.SetValueConditional((void*)3, (void*)6);
+		EATEST_VERIFY_MSG(bWasEqualTo6, "AtomicPointer failure.");
+	}
+
+	{
+		AtomicInt32 gA, gB;
+
+		gA = gB = 0;
+		++gA;
+		++gB;
+		gA = gB = 0;
+
+		EATEST_VERIFY_MSG((gA == 0) && (gB == 0), "AtomicInt32 operator= failure.");
+	}
+
+	#if EA_THREADS_AVAILABLE
+
+		{  // Multithreaded test 1
+			AWorkData32 workData32;
+
+			const int kThreadCount(kMaxConcurrentThreadCount - 1);
+			Thread thread[kThreadCount];
+			Thread::Status status;
+
+			for(int i(0); i < kThreadCount; i++)
+				thread[i].Begin(Atomic32TestThreadFunction1, &workData32);
+
+			EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000);
+
+			workData32.mbShouldQuit = true;
+
+			for(int i(0); i < kThreadCount; i++)
+			{
+				status = thread[i].WaitForEnd(GetThreadTime() + 30000);
+				EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure.");
+			}
+
+			// In the end, the sum must be zero.
+			EATEST_VERIFY_MSG(workData32.mnAtomicInteger1 == 0, "Atomic/Thread failure.");
+			EATEST_VERIFY_MSG(workData32.mnAtomicInteger2 == 0, "Atomic/Thread failure.");
+
+			nErrorCount += (int)workData32.mnErrorCount;
+		}
+
+		{  // Multithreaded test 2
+			AWorkData32    workData32;
+			const int      kThreadCount(kMaxConcurrentThreadCount - 1);
+			Thread         thread[kThreadCount];
+			Thread::Status status;
+
+			for(int i(0); i < kThreadCount; i++)
+				thread[i].Begin(Atomic32TestThreadFunction2, &workData32);
+
+			EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000);
+
+			workData32.mbShouldQuit = true;
+
+			for(int i(0); i < kThreadCount; i++)
+			{
+				status = thread[i].WaitForEnd(GetThreadTime() + 30000);
+				EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure.");
+			}
+
+			nErrorCount += (int)workData32.mnErrorCount;
+		}
+	
+
+		{  // Multithreaded test 1
+			AWorkData64 workData64;
+
+			const int kThreadCount(kMaxConcurrentThreadCount - 1);
+			Thread thread[kThreadCount];
+			Thread::Status status;
+
+			for(int i(0); i < kThreadCount; i++)
+				thread[i].Begin(Atomic64TestThreadFunction1, &workData64);
+
+			EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000);
+
+			workData64.mbShouldQuit = true;
+
+			for(int i(0); i < kThreadCount; i++)
+			{
+				status = thread[i].WaitForEnd(GetThreadTime() + 30000);
+				EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure.");
+			}
+
+			// In the end, the sum must be zero.
+			EATEST_VERIFY_MSG(workData64.mnAtomicInteger1 == 0, "Atomic/Thread failure.");
+			EATEST_VERIFY_MSG(workData64.mnAtomicInteger2 == 0, "Atomic/Thread failure.");
+
+			nErrorCount += (int)workData64.mnErrorCount;
+		}
+
+		{  // Multithreaded test 2
+			AWorkData64    workData64;
+			const int      kThreadCount(kMaxConcurrentThreadCount - 1);
+			Thread         thread[kThreadCount];
+			Thread::Status status;
+
+			for(int i(0); i < kThreadCount; i++)
+				thread[i].Begin(Atomic64TestThreadFunction2, &workData64);
+
+			EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000);
+
+			workData64.mbShouldQuit = true;
+
+			for(int i(0); i < kThreadCount; i++)
+			{
+				status = thread[i].WaitForEnd(GetThreadTime() + 30000);
+				EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure.");
+			}
+
+			nErrorCount += (int)workData64.mnErrorCount;
+		}
+
+		{  // Multithreaded test 3
+			AWorkData64    workData64;
+			const int      kThreadCount(kMaxConcurrentThreadCount - 1);
+			Thread         thread[kThreadCount];
+			Thread::Status status;
+
+			for(int i(0); i < kThreadCount; i++)
+				thread[i].Begin(Atomic64TestThreadFunction3, &workData64);
+
+			EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000);
+
+			workData64.mbShouldQuit = true;
+
+			for(int i(0); i < kThreadCount; i++)
+			{
+				status = thread[i].WaitForEnd(GetThreadTime() + 30000);
+				EATEST_VERIFY_MSG(status != EA::Thread::Thread::kStatusRunning, "Atomic/Thread failure.");
+			}
+
+			nErrorCount += (int)workData64.mnErrorCount;
+		}
+
+		#endif
+
+		{
+			nErrorCount += TestAtomicIntT<short>();
+			nErrorCount += TestAtomicIntT<unsigned short>();
+			nErrorCount += TestAtomicIntT<int>();
+			nErrorCount += TestAtomicIntT<unsigned int>();
+			nErrorCount += TestAtomicIntT<long>();
+			nErrorCount += TestAtomicIntT<unsigned long>();
+			nErrorCount += TestAtomicIntT<intptr_t>();
+			nErrorCount += TestAtomicIntT<uintptr_t>();
+			nErrorCount += TestAtomicIntT<size_t>();
+			nErrorCount += TestAtomicIntT<int16_t>();
+			nErrorCount += TestAtomicIntT<uint16_t>();
+			nErrorCount += TestAtomicIntT<int32_t>();
+			nErrorCount += TestAtomicIntT<uint32_t>();
+			nErrorCount += TestAtomicIntT<char32_t>();
+			nErrorCount += TestAtomicIntT<long long>();
+			nErrorCount += TestAtomicIntT<unsigned long long>();
+			nErrorCount += TestAtomicIntT<int64_t>();
+			nErrorCount += TestAtomicIntT<uint64_t>();
+		}
+
+		// Non-Member Atomics Tests 
+		{
+			nErrorCount += TestNonMemberAtomics<short>();
+			nErrorCount += TestNonMemberAtomics<unsigned short>();
+			nErrorCount += TestNonMemberAtomics<int>();
+			nErrorCount += TestNonMemberAtomics<unsigned int>();
+			nErrorCount += TestNonMemberAtomics<long>();
+			nErrorCount += TestNonMemberAtomics<unsigned long>();
+			nErrorCount += TestNonMemberAtomics<intptr_t>();
+			nErrorCount += TestNonMemberAtomics<uintptr_t>();
+			nErrorCount += TestNonMemberAtomics<size_t>();
+			nErrorCount += TestNonMemberAtomics<int16_t>();
+			nErrorCount += TestNonMemberAtomics<uint16_t>();
+			nErrorCount += TestNonMemberAtomics<int32_t>();
+			nErrorCount += TestNonMemberAtomics<uint32_t>();
+			nErrorCount += TestNonMemberAtomics<char32_t>();
+			nErrorCount += TestNonMemberAtomics<long long>();
+			nErrorCount += TestNonMemberAtomics<unsigned long long>();
+			nErrorCount += TestNonMemberAtomics<int64_t>();
+			nErrorCount += TestNonMemberAtomics<uint64_t>();
+		}
+
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+
+
+
+

+ 157 - 0
test/thread/source/TestThreadBarrier.cpp

@@ -0,0 +1,157 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_thread.h>
+#include <eathread/eathread_mutex.h>
+#include <eathread/eathread_barrier.h>
+#include <stdlib.h>
+
+
+using namespace EA::Thread;
+
+
+const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+
+struct BT_WorkData
+{
+	Barrier*    mpBarrier; 
+	int         mnSleepTime; 
+	AtomicInt32 mnErrorCount;
+
+	BT_WorkData(Barrier* pBarrier = NULL, int nSleepTime = 0)
+	  : mpBarrier(pBarrier), mnSleepTime(nSleepTime), mnErrorCount(0) {}
+};
+
+
+
+static intptr_t BT_ConsumerFunction(void* pvWorkData)
+{
+	int            nErrorCount = 0;
+	BT_WorkData*   pWorkData   = (BT_WorkData*)pvWorkData;
+	const ThreadId threadId    = GetThreadId();
+
+	EA::UnitTest::ReportVerbosity(1, "Barrier consumer test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	// Here we would actually do the job, but printing 'job done' is enough in itself.
+	ThreadSleep((ThreadTime)pWorkData->mnSleepTime);
+	EA::UnitTest::ReportVerbosity(1, "Job done by thread %s.\n", EAThreadThreadIdToString(threadId));
+	EA::UnitTest::ReportVerbosity(1, "Start synchronizing by thread %s.\n", EAThreadThreadIdToString(threadId));
+	const Barrier::Result result = pWorkData->mpBarrier->Wait(GetThreadTime() + 10000); 
+
+	if(result == Barrier::kResultPrimary)
+	{
+		// This is the first thread to be released: call producer function
+		EA::UnitTest::ReportVerbosity(1, "Serial execution at Barrier by thread %s\n", EAThreadThreadIdToString(threadId));
+	}
+	else if(result == Barrier::kResultTimeout)
+	{
+		EA::UnitTest::Report("Barrier time-out by thread %s\n", EAThreadThreadIdToString(threadId));
+		nErrorCount++; 
+	}
+	else if(result == Barrier::kResultError)
+	{
+		EA::UnitTest::Report("Barrier error in thread %s\n", EAThreadThreadIdToString(threadId));
+		nErrorCount++; 
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	ThreadSleep((ThreadTime)pWorkData->mnSleepTime);
+
+	EA::UnitTest::ReportVerbosity(1, "Job synchronized by thread %s.\n", EAThreadThreadIdToString(threadId));
+	return 0;
+}
+
+
+static intptr_t BT_ConsumerFunction2(void * pvWorkData)
+{
+	((Barrier*)pvWorkData)->Wait();
+	((Barrier*)pvWorkData)->Wait();
+	return 0;
+}
+
+
+int TestThreadBarrier()
+{
+	int nErrorCount(0);
+
+	#if EA_THREADS_AVAILABLE
+
+		{
+			Thread::Status status;
+			const int      kThreadCount(kMaxConcurrentThreadCount - 1);
+			BT_WorkData    workData[kThreadCount];
+			Thread         threads[kThreadCount];
+			ThreadId       threadId[kThreadCount];
+			Barrier        barrier(kThreadCount); 
+			int            i;
+
+			for(i = 0; i < kThreadCount; i++)
+			{
+				workData[i].mpBarrier = &barrier;
+				workData[i].mnSleepTime = (i + 1) * 500;
+			}
+
+			for(i = 0; i < kThreadCount; i++)
+			{
+				threadId[i] = threads[i].Begin(BT_ConsumerFunction, &workData[i]);
+				EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Barrier/Thread failure: Couldn't create thread.");
+			}
+
+			EA::UnitTest::ThreadSleepRandom(2000,  2000);
+
+			for(i = 0; i < kThreadCount; i++)
+			{
+				if(threadId[i] != kThreadIdInvalid)
+				{
+					status = threads[i].WaitForEnd(GetThreadTime() + 30000);
+					EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Barrier/Thread failure.");
+				}
+
+				nErrorCount += workData[i].mnErrorCount;
+			}
+		}
+
+		{
+			Thread         threads[2];
+			ThreadId       threadId[2];
+			Barrier        barrier(2);
+			Thread::Status status;
+
+			threadId[0] = threads[0].Begin(BT_ConsumerFunction2, &barrier);
+			EATEST_VERIFY_MSG(threadId[0] != kThreadIdInvalid, "Barrier/Thread failure: Couldn't create thread.");
+
+			threadId[1] = threads[1].Begin(BT_ConsumerFunction2, &barrier);
+			EATEST_VERIFY_MSG(threadId[1] != kThreadIdInvalid, "Barrier/Thread failure: Couldn't create thread.");
+
+			if(threadId[0] != kThreadIdInvalid)
+			{
+				status = threads[0].WaitForEnd();
+				EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Barrier/Thread failure.");
+			}
+
+			if(threadId[1] != kThreadIdInvalid)
+			{
+				status = threads[1].WaitForEnd();
+				EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Barrier/Thread failure.");
+			}
+		}
+
+	#endif
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+
+

+ 391 - 0
test/thread/source/TestThreadCallstack.cpp

@@ -0,0 +1,391 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread.h>
+#include <eathread/eathread_callstack.h>
+#include <eathread/eathread_callstack_context.h>
+#include <EASTL/fixed_vector.h>
+#include <EASTL/fixed_string.h>
+#include <EATest/EATest.h>
+#include <EAStdC/EAStopwatch.h>
+#include <EAStdC/EASprintf.h>
+#include <EASTL/set.h>
+#include <eathread/eathread_thread.h>
+#include <eathread/eathread_sync.h>
+#include <eathread/eathread_semaphore.h>
+
+
+#ifdef _MSC_VER
+#pragma warning(push, 0)
+#include <Windows.h>
+#endif
+
+
+struct CallstackTestInfo
+{
+	eastl::fixed_vector<void*, 32> mAddressSet;
+};
+
+struct CallstackTestThreadContext
+{
+	CallstackTestInfo* 				mCallstackTestInfo;
+	int*							mnErrorCount;
+	EA::Thread::CallstackContext	mCallstackContext;
+	EA::Thread::Semaphore			mThreadControlSema;
+	EA::Thread::Semaphore			mTestStartSema;
+};
+
+#define EACALLSTACK_TEST_FUNCTION_LINKAGE
+EA_NO_INLINE void TestCallstack01(CallstackTestInfo& callstackInfo, int& nErrorCount);
+EA_NO_INLINE void TestCallstack02(CallstackTestInfo& callstackInfo, int& nErrorCount);
+EA_NO_INLINE void TestCallstack03(CallstackTestInfo& callstackInfo, int& nErrorCount);
+EA_NO_INLINE void TestCallstack04(CallstackTestInfo& callstackInfo, int& nErrorCount);
+EA_NO_INLINE void TestCallstackWithContext02(CallstackTestThreadContext* context);
+EA_NO_INLINE void TestCallstackWithContext01(CallstackTestThreadContext* context);
+EA_NO_INLINE intptr_t TestCallstackContextThreadFunc(void* context);
+
+
+static void VerifyCallstack(CallstackTestInfo& callstackInfo, EA::Thread::CallstackContext* context, int& nErrorCount)
+{
+	// We don't do a rigorous per entry ordered match because we've found that 
+	// compiler optimizations get in the way of testing that reliably.
+	void*        addressArray[24] = {};
+	size_t       addressCount = EA::Thread::GetCallstack(addressArray, EAArrayCount(addressArray), context);
+	eastl_size_t matchCount = 0;
+	
+	for(eastl_size_t i = 0, iEnd = callstackInfo.mAddressSet.size(); i != iEnd; i++)
+	{
+		void*  p = callstackInfo.mAddressSet[i];
+		size_t j;
+
+		for(j = 0; j < addressCount; j++)
+		{
+			if(abs((int)((intptr_t)addressArray[j] - (intptr_t)p)) < 512)
+			{
+				matchCount++;
+				break;
+			}
+		}
+	}
+
+	if(matchCount != callstackInfo.mAddressSet.size())
+	{
+		eastl::fixed_string<char, 256> sExpectedCallstack;
+		eastl::fixed_string<char, 256> sReportedCallstack;
+
+		for(eastl_size_t i = 0; i < callstackInfo.mAddressSet.size(); i++)
+			sExpectedCallstack.append_sprintf("%p ", callstackInfo.mAddressSet[i]);
+
+		for(size_t i = 0; i < addressCount; i++)
+			sReportedCallstack.append_sprintf("%p ", addressArray[i]);
+
+			EATEST_VERIFY_F(matchCount == callstackInfo.mAddressSet.size(), "VerifyCallstack failure. Each member from the expected callstack should be present (+/- 512 bytes) in the reported callstack.\n    Expected callstack %s\n    Reported callstack %s", sExpectedCallstack.c_str(), sReportedCallstack.c_str());
+	}
+
+	EA::UnitTest::NonInlinableFunction();
+}
+
+EA_DISABLE_VC_WARNING(4740);	// flow in or out of inline asm code suppresses global optimization
+
+EA_NO_INLINE EACALLSTACK_TEST_FUNCTION_LINKAGE void TestCallstackWithContext02(CallstackTestThreadContext* context)
+{
+	int nErrorCount = 0;
+	EA::UnitTest::NonInlinableFunction();
+	
+	EATEST_VERIFY(EA::Thread::GetCallstackContextSysThreadId(context->mCallstackContext, (intptr_t)EA::Thread::GetSysThreadId()));
+	context->mnErrorCount += nErrorCount;
+
+	context->mTestStartSema.Post();
+	context->mThreadControlSema.Wait();
+}
+
+EA_NO_INLINE EACALLSTACK_TEST_FUNCTION_LINKAGE void TestCallstackWithContext01(CallstackTestThreadContext* context)
+{
+	void* pAddress;
+
+	EA::UnitTest::NonInlinableFunction();
+	EAGetInstructionPointer(pAddress);
+	context->mCallstackTestInfo->mAddressSet.push_back(pAddress);
+
+	// calling out function through a conditionally set functor to help with optimizations from inlining this code
+	TestCallstackWithContext02(context);
+}
+
+EA_NO_INLINE intptr_t TestCallstackContextThreadFunc(void* context)
+{
+	CallstackTestThreadContext* threadContext = (CallstackTestThreadContext*)context;
+
+	threadContext->mThreadControlSema.Wait();
+
+	void* pAddress;
+	EAGetInstructionPointer(pAddress);
+	threadContext->mCallstackTestInfo->mAddressSet.push_back(pAddress);
+
+	EA::UnitTest::NonInlinableFunction();
+	TestCallstackWithContext01(threadContext);
+
+	return 0;
+}
+
+EA_NO_INLINE EACALLSTACK_TEST_FUNCTION_LINKAGE void TestCallstack04(CallstackTestInfo& callstackInfo, int& nErrorCount)
+{
+	void* pAddress;
+	EAGetInstructionPointer(pAddress);
+	callstackInfo.mAddressSet.push_back(pAddress);
+
+	VerifyCallstack(callstackInfo, nullptr, nErrorCount);
+
+	EA::UnitTest::NonInlinableFunction();
+}
+
+EA_NO_INLINE EACALLSTACK_TEST_FUNCTION_LINKAGE void TestCallstack03(CallstackTestInfo& callstackInfo, int& nErrorCount)
+{
+	void* pAddress;
+	EAGetInstructionPointer(pAddress);
+	callstackInfo.mAddressSet.push_back(pAddress);
+
+	VerifyCallstack(callstackInfo, nullptr, nErrorCount);
+	TestCallstack04(callstackInfo, nErrorCount);
+	EA::UnitTest::NonInlinableFunction();
+}
+
+EA_NO_INLINE EACALLSTACK_TEST_FUNCTION_LINKAGE void TestCallstack02(CallstackTestInfo& callstackInfo, int& nErrorCount)
+{
+	void* pAddress;
+	EAGetInstructionPointer(pAddress);
+	callstackInfo.mAddressSet.push_back(pAddress);
+
+	VerifyCallstack(callstackInfo, nullptr, nErrorCount);
+	TestCallstack03(callstackInfo, nErrorCount);
+	EA::UnitTest::NonInlinableFunction();
+}
+
+EA_NO_INLINE EACALLSTACK_TEST_FUNCTION_LINKAGE void TestCallstack01(CallstackTestInfo& callstackInfo, int& nErrorCount)
+{
+	void* pAddress;
+	EAGetInstructionPointer(pAddress);
+	callstackInfo.mAddressSet.push_back(pAddress);
+
+	VerifyCallstack(callstackInfo, nullptr, nErrorCount);
+	TestCallstack02(callstackInfo, nErrorCount);
+	EA::UnitTest::NonInlinableFunction();
+}
+
+EA_RESTORE_VC_WARNING();
+
+
+/// EATHREAD_GETCALLSTACK_RELIABLE
+///
+/// Defined as 0 or 1
+/// Identifies whether we can rely on the results of GetCallstack for the purposes
+/// of this unit test. 
+#if !defined(EATHREAD_GETCALLSTACK_RELIABLE)
+	#if EATHREAD_GETCALLSTACK_SUPPORTED
+		#if defined(EA_PLATFORM_WINRT) && defined(EA_PROCESSOR_X86)  // WinRT-x86 does not provide usable callstacks so we avoid tracing them on this platform.
+			#define EATHREAD_GETCALLSTACK_RELIABLE 0
+		#else
+			#define EATHREAD_GETCALLSTACK_RELIABLE 1
+		#endif
+	#else
+		#define EATHREAD_GETCALLSTACK_RELIABLE 0
+	#endif
+#endif
+
+static bool IsRoughlyEqualAddress(void* a, void* b)
+{
+	static const uintptr_t kMaxBytesDist = 512;
+	if ( ((uintptr_t)a -(uintptr_t)b)  <= kMaxBytesDist)
+	{
+		return true;
+	}
+	else if (((uintptr_t)b - (uintptr_t)a) <= kMaxBytesDist)
+	{
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+EA_NO_INLINE EACALLSTACK_TEST_FUNCTION_LINKAGE  int TestRemoteThreadContextVsCallstack()
+{
+	int nErrorCount(0);
+
+	struct CallstackTestThread : public EA::Thread::IRunnable
+	{
+		EA::Thread::Thread           mThread;
+		EA::Thread::ThreadParameters mParameters;
+		EA::Thread::Semaphore        mEndSemaphore;
+		EA::Thread::Semaphore		mStartSemaphore;
+		char                         mThreadName[16];
+		uint64_t                     mCounter;
+		volatile bool                mbShouldRun;
+		void*					mAddressCallstackArray[64];
+
+		CallstackTestThread() : mThread(), mParameters(), mEndSemaphore(0), mStartSemaphore(0), mCounter(0), mbShouldRun(true) 
+		{
+			eastl::fill(eastl::begin(mAddressCallstackArray), eastl::end(mAddressCallstackArray), (void*)nullptr);
+		}
+
+		CallstackTestThread(const CallstackTestThread&) {}
+		void operator=(const CallstackTestThread&) {}
+
+
+		intptr_t Run(void*)
+		{
+			EA::Thread::GetCallstack(mAddressCallstackArray, EAArrayCount(mAddressCallstackArray));
+			mStartSemaphore.Post();
+			mEndSemaphore.Wait();
+			return 0;
+		}
+	};
+
+
+	CallstackTestThread remoteThread;
+	remoteThread.mThread.Begin(&remoteThread, NULL, &remoteThread.mParameters);
+
+	// make sure this thread is running
+	remoteThread.mStartSemaphore.Wait();
+
+	// context
+	void*        addressContextArray[64] = {nullptr};
+	auto threadId = remoteThread.mThread.GetId();
+
+	{
+#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_CAPILANO)
+		// suspend the target thread to make sure we get a coherent callstack
+		bool wasSuspended = (::SuspendThread(threadId) != ((DWORD)-1)); // fail is (DWORD)-1
+#endif
+
+		EA::Thread::CallstackContext callstackContext;
+		if (EA::Thread::GetCallstackContext(callstackContext, (intptr_t)threadId))
+		{
+			EA::Thread::GetCallstack(addressContextArray, EAArrayCount(addressContextArray), &callstackContext);
+		}
+
+#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_CAPILANO)
+		// resume the target thread as needed
+		if (wasSuspended)
+		{
+			::ResumeThread(threadId);
+		}
+#endif
+	}
+	remoteThread.mEndSemaphore.Post();
+
+	// make sure every address in the local callstack is in the remote. (remote is a superset of function calls because of suspended in kernel)
+	for (void* localAddress: remoteThread.mAddressCallstackArray)
+	{
+		if (eastl::find_if(eastl::begin(addressContextArray), eastl::end(addressContextArray),
+			[=](void* a) { return  IsRoughlyEqualAddress(a, localAddress); }) == eastl::end(addressContextArray))
+		{
+			nErrorCount++;
+		}
+	}
+
+	remoteThread.mThread.WaitForEnd();
+
+	return nErrorCount;
+}
+
+int TestThreadCallstack()
+{
+	int nErrorCount(0);
+
+	#if EATHREAD_GETCALLSTACK_RELIABLE
+		{
+			EA::Thread::InitCallstack();
+
+			CallstackTestInfo info;
+			TestCallstack01(info, nErrorCount);
+			EA::Thread::ShutdownCallstack();
+		}
+
+		#if defined(EA_PLATFORM_WIN64) || defined(EA_PLATFORM_CAPILANO) ||  defined(EA_PLATFORM_KETTLE)
+		// This test will spawn a thread which will grab its own context and provide it to the main thread 
+		// to use when generating a callstack. We use semaphores to control the created thread to ensure 
+		// the thread is alive while we call GetCallstack() inside of VerifyCallstack()
+		{
+			EA::Thread::InitCallstack();
+
+			EA::Thread::Thread testThread;
+			CallstackTestInfo info;
+			CallstackTestThreadContext threadContext;
+			threadContext.mCallstackTestInfo = &info;
+			threadContext.mnErrorCount = &nErrorCount;
+
+			testThread.Begin(TestCallstackContextThreadFunc, (void*)&threadContext);
+			while (testThread.GetStatus() != EA::Thread::Thread::kStatusRunning)
+			{
+				EA::Thread::ThreadSleep();
+			}
+			
+			// Let the test thread proceed in generating test data
+			threadContext.mThreadControlSema.Post();
+
+			// Wait until the test thread is done entering test data
+			threadContext.mTestStartSema.Wait();
+
+			// Grab the context of the testThread and verify the callstack is what we expect
+			VerifyCallstack(*threadContext.mCallstackTestInfo, &threadContext.mCallstackContext, nErrorCount);
+
+			// Let the test thread finish
+			threadContext.mThreadControlSema.Post();
+			testThread.WaitForEnd();
+
+			EA::Thread::ShutdownCallstack();
+		}
+
+		{
+			EA::Thread::InitCallstack();
+
+			const int numErrorInRemoteTest = TestRemoteThreadContextVsCallstack();
+			#if defined(EA_PLATFORM_KETTLE) // We know that kettle cannot do remote callstacks. This is just to check that it does not crash when attempting to do a remote callstack
+				EATEST_VERIFY(numErrorInRemoteTest != 0);
+			#else
+				nErrorCount += numErrorInRemoteTest;
+			#endif
+
+			EA::Thread::ShutdownCallstack();
+		}
+		#endif
+	#endif
+
+
+	#if defined(EA_PLATFORM_MICROSOFT)
+		// bool ThreadHandlesAreEqual(intptr_t threadId1, intptr_t threadId2);
+		// uint32_t GetThreadIdFromThreadHandle(intptr_t threadId);
+	#endif
+
+	// To do: Implement tests for the following for supporting platforms.
+	// bool GetCallstackContext(CallstackContext& context, intptr_t threadId = 0);
+	// bool GetCallstackContextSysThreadId(CallstackContext& context, intptr_t sysThreadId = 0);
+	// void GetCallstackContext(CallstackContext& context, const Context* pContext = NULL);
+	// size_t GetModuleFromAddress(const void* pAddress, char* pModuleFileName, size_t moduleNameCapacity);
+	// ModuleHandle GetModuleHandleFromAddress(const void* pAddress);
+
+	// EA::Thread::CallstackContext context;
+	// EA::Thread::GetCallstackContext(context, EA::Thread::GetThreadId());
+	// EATEST_VERIFY(context.mRIP != 0);
+	// EATEST_VERIFY(context.mRSP != 0);
+
+	// To consider: Test SetStackBase. It's not simple because SetStackBase is a backup for 
+	// when GetStackBase's default functionality doesn't happen to work.
+	// void  SetStackBase(void* pStackBase);
+	// void  SetStackBase(uintptr_t pStackBase){ SetStackBase((void*)pStackBase); }
+
+	void* pStackBase  = EA::Thread::GetStackBase();
+	void* pStackLimit = EA::Thread::GetStackLimit();
+
+	if(pStackBase && pStackLimit)
+	{
+		EATEST_VERIFY((uintptr_t)&nErrorCount < (uintptr_t)pStackBase);
+		EATEST_VERIFY((uintptr_t)&nErrorCount > (uintptr_t)pStackLimit);
+	}
+
+	return nErrorCount;
+}

+ 335 - 0
test/thread/source/TestThreadCondition.cpp

@@ -0,0 +1,335 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_condition.h>
+#include <eathread/eathread_thread.h>
+#include <eathread/eathread_mutex.h>
+#include <eathread/eathread_list.h>
+#include <eathread/eathread_sync.h>
+#include <stdlib.h>
+#include <time.h>
+
+
+using namespace EA::Thread;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// EATHREAD_INTERPROCESS_CONDITION_SUPPORTED
+//
+#ifndef EATHREAD_INTERPROCESS_CONDITION_SUPPORTED
+	#if defined(EA_PLATFORM_MICROSOFT) || defined(EA_PLATFORM_LINUX)
+		#define EATHREAD_INTERPROCESS_CONDITION_SUPPORTED 1
+	#else
+		#define EATHREAD_INTERPROCESS_CONDITION_SUPPORTED 0
+	#endif
+#endif
+ 
+
+const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+
+struct TMWorkData
+{
+	volatile bool                mbProducersShouldQuit;
+	volatile bool                mbConsumersShouldQuit;
+	EA::Thread::simple_list<int> mJobList;
+	Condition                    mCondition;
+	Mutex                        mMutex;
+	int                          mnLastJobID;
+	int                          mnConditionTimeout;
+	AtomicInt32                  mnTotalJobsCreated;
+	AtomicInt32                  mnTotalJobsCompleted;
+
+	TMWorkData( const ConditionParameters* pCondParams ) : mbProducersShouldQuit(false), mbConsumersShouldQuit(false), mCondition( pCondParams ), 
+				   mMutex(NULL, true), mnLastJobID(0), mnConditionTimeout(60000), mnTotalJobsCreated(0), mnTotalJobsCompleted(0)
+	{
+		// Empty
+	}
+
+	// define copy ctor and assignment operator
+	// so the compiler does define them intrisically
+	TMWorkData(const TMWorkData& rhs);               // copy constructor
+	TMWorkData& operator=(const TMWorkData& rhs);    // assignment operator
+};
+
+
+static intptr_t ProducerFunction(void* pvWorkData)
+{
+	int         nErrorCount = 0;
+	TMWorkData* pWorkData   = (TMWorkData*)pvWorkData;
+	ThreadId    threadId    = GetThreadId();
+
+	EA::UnitTest::ReportVerbosity(1, "Condition producer test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	EAReadWriteBarrier();
+
+	while(!pWorkData->mbProducersShouldQuit)
+	{
+		EA::UnitTest::ThreadSleepRandom(100, 200);
+		pWorkData->mMutex.Lock();
+
+		for(int i(0), iEnd(rand() % 3); i < iEnd; i++)
+		{
+			const int nJob(++pWorkData->mnLastJobID);
+			pWorkData->mJobList.push_back(nJob);
+			++pWorkData->mnTotalJobsCreated;
+			EA::UnitTest::ReportVerbosity(1, "Job %d created by %s.\n", nJob, EAThreadThreadIdToString(threadId));
+			ThreadCooperativeYield(); // Used by cooperative threading platforms.
+		}
+
+		pWorkData->mMutex.Unlock();
+		pWorkData->mCondition.Signal(false);
+		ThreadCooperativeYield(); // Used by cooperative threading platforms.
+	}
+
+	EA::UnitTest::ReportVerbosity(1, "Producer exiting: %s.\n", EAThreadThreadIdToString(threadId));
+
+	return nErrorCount;
+}
+
+static intptr_t ProducerFunction_DoesNotSignal(void* pvWorkData)
+{
+	int         nErrorCount = 0;
+	TMWorkData* pWorkData   = (TMWorkData*)pvWorkData;
+	ThreadId    threadId    = GetThreadId();
+	EA_UNUSED(pWorkData);
+
+	EA::UnitTest::ReportVerbosity(1, "Condition producer (does not signal) test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	// Intentionally  do nothing here.  We are testing the conditional variable time out code path by
+	// ensuring we do not signal the Consumer that any work has been added into the queue for them
+	// to consume therefor explicitly causing a condition variable timeout.
+
+	EA::UnitTest::ReportVerbosity(1, "Producer (does not signal) exiting: %s.\n", EAThreadThreadIdToString(threadId));
+
+	return nErrorCount;
+}
+
+static intptr_t ConsumerFunction(void* pvWorkData)
+{
+	int         nErrorCount = 0;
+	TMWorkData* pWorkData   = (TMWorkData*)pvWorkData;
+	ThreadId    threadId    = GetThreadId();
+
+	EA::UnitTest::ReportVerbosity(1, "Condition producer test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	pWorkData->mMutex.Lock();
+
+	do{
+		if(!pWorkData->mJobList.empty())
+		{
+			const int nJob = pWorkData->mJobList.front();
+			pWorkData->mJobList.pop_front();
+			pWorkData->mMutex.Unlock();
+
+			ThreadCooperativeYield(); // Used by cooperative threading platforms.
+
+			// Here we would actually do the job, but printing 'job done' is enough in itself.
+			++pWorkData->mnTotalJobsCompleted;
+			EA::UnitTest::ReportVerbosity(1, "Job %d done by %s.\n", nJob, EAThreadThreadIdToString(threadId));
+
+			pWorkData->mMutex.Lock();
+		}
+		else
+		{
+			const ThreadTime timeoutAbsolute = GetThreadTime() + pWorkData->mnConditionTimeout;
+			const Condition::Result result = pWorkData->mCondition.Wait(&pWorkData->mMutex, timeoutAbsolute);
+			if((result != Condition::kResultOK) && pWorkData->mJobList.empty())
+				break;
+		}
+	}while(!pWorkData->mbConsumersShouldQuit || !pWorkData->mJobList.empty());
+
+	pWorkData->mMutex.Unlock();
+
+	EA::UnitTest::ReportVerbosity(1, "Consumer exiting: %s.\n", EAThreadThreadIdToString(threadId));
+
+	return nErrorCount;
+}
+
+
+int TestThreadCondition()
+{
+	int nErrorCount(0);
+
+	{ // ctor tests
+		// We test various combinations of Mutex ctor and ConditionParameters.
+		// ConditionParameters(bool bIntraProcess = true, const char* pName = NULL);
+		// Condition(const ConditionParameters* pConditionParameters = NULL, bool bDefaultParameters = true);
+
+		ConditionParameters cp1(true, NULL);
+		ConditionParameters cp2(true, "EATcp2");
+
+	  #if EATHREAD_INTERPROCESS_CONDITION_SUPPORTED
+		ConditionParameters cp3(false, NULL);
+		ConditionParameters cp4(false, "EATcp4");
+	  #else
+		ConditionParameters cp3(true, NULL);
+		ConditionParameters cp4(true, "EATcp4");
+	  #endif
+
+		// Create separate scopes below because some platforms are so  
+		// limited that they can't create all of them at once.
+		{
+			Condition cond1(&cp1, false);
+			Condition cond2(&cp2, false);
+			Condition cond3(&cp3, false);
+
+			cond1.Signal();
+			cond2.Signal();
+			cond3.Signal();
+		}
+		{
+			Condition cond4(&cp4, false);
+			Condition cond5(NULL, true);
+			Condition cond6(NULL, false);
+			cond6.Init(&cp1);
+
+			cond4.Signal();
+			cond5.Signal();
+			cond6.Signal();
+		}
+	}
+
+
+	#if EA_THREADS_AVAILABLE
+		{
+			// test producer/consumer wait condition with intra-process condition
+			{
+				ConditionParameters exlusiveConditionParams( true, NULL );
+				TMWorkData workData( &exlusiveConditionParams );
+				Thread::Status status;
+				int i;
+	
+				const int kThreadCount(kMaxConcurrentThreadCount - 1);
+				Thread    threadProducer[kThreadCount];
+				ThreadId  threadIdProducer[kThreadCount];
+				Thread    threadConsumer[kThreadCount];
+				ThreadId  threadIdConsumer[kThreadCount];
+	
+				// Create producers and consumers.
+				for(i = 0; i < kThreadCount; i++)
+				{
+					threadIdProducer[i] = threadProducer[i].Begin(ProducerFunction, &workData);
+					EATEST_VERIFY_MSG(threadIdProducer[i] != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
+	
+					threadIdConsumer[i] = threadConsumer[i].Begin(ConsumerFunction, &workData);
+					EATEST_VERIFY_MSG(threadIdConsumer[i] != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
+				}
+	
+				EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
+	
+				// Wait for producers to quit.
+				workData.mbProducersShouldQuit = true;
+				for(i = 0; i < kThreadCount; i++)
+				{
+					if(threadIdProducer[i] != kThreadIdInvalid)
+					{
+						status = threadProducer[i].WaitForEnd(GetThreadTime() + 30000);
+						EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for producer end failed.");
+					}
+				}
+	
+				EA::UnitTest::ThreadSleepRandom(2000, 2000);
+	
+				// Wait for consumers to quit.
+				workData.mbConsumersShouldQuit = true;
+				workData.mCondition.Signal(true);
+				for(i = 0; i < kThreadCount; i++)
+				{
+					if(threadIdConsumer[i] != kThreadIdInvalid)
+					{
+						status = threadConsumer[i].WaitForEnd(GetThreadTime() + 30000);
+						EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for consumer end failed.");
+					}
+				}
+	
+				EATEST_VERIFY_MSG(workData.mnTotalJobsCreated == workData.mnTotalJobsCompleted, "Condition failure: Not all consumer work was processed.");
+			}
+
+			// test single producer/ single consumer wait condition with inter-process condition
+			#if /*EATHREAD_INTERPROCESS_CONDITION_SUPPORTED*/ 0 // Disabled because this code fails on most platforms.
+			{
+				ConditionParameters sharedConditionParams( false, NULL );
+				TMWorkData     workData( &sharedConditionParams ); // Inter-process.
+				Thread::Status status;
+				Thread         threadProducer;
+				Thread         threadConsumer;
+	
+				ThreadId threadIdProducer = threadProducer.Begin(ProducerFunction, &workData);
+				EATEST_VERIFY_MSG(threadIdProducer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
+	
+				ThreadId threadIdConsumer = threadConsumer.Begin(ConsumerFunction, &workData);
+				EATEST_VERIFY_MSG(threadIdConsumer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
+				
+				EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
+	
+				// Wait for producer to quit.
+				workData.mbProducersShouldQuit = true;
+
+				if(threadIdProducer != kThreadIdInvalid)
+				{
+					status = threadProducer.WaitForEnd(GetThreadTime() + 30000);
+					EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for producer end failed.");
+				}
+
+				EA::UnitTest::ThreadSleepRandom(2000, 2000);
+	
+				// Wait for consumers to quit.
+				workData.mbConsumersShouldQuit = true;
+				workData.mCondition.Signal(true);
+				if(threadIdConsumer != kThreadIdInvalid)
+				{
+					status = threadConsumer.WaitForEnd(GetThreadTime() + 30000);
+					EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for consumer end failed.");
+				}
+	
+				EATEST_VERIFY_MSG(workData.mnTotalJobsCreated == workData.mnTotalJobsCompleted, "Condition failure: Not all consumer work was processed.");
+			}
+			#endif
+			
+			// Test conditional variable timeout explicitly by not sending a signal.
+			{
+				//::EA::EAMain::SetVerbosity(5);
+				ConditionParameters sharedConditionParams( true, NULL );
+				TMWorkData     workData( &sharedConditionParams ); // Inter-process.
+				workData.mnConditionTimeout = 3000;  // timeout value has to be less than thread timeout value below.
+				Thread::Status status;
+				Thread         threadProducer;
+				Thread         threadConsumer;
+	
+				ThreadId threadIdProducer = threadProducer.Begin(ProducerFunction_DoesNotSignal, &workData);
+				EATEST_VERIFY_MSG(threadIdProducer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
+	
+				ThreadId threadIdConsumer = threadConsumer.Begin(ConsumerFunction, &workData);
+				EATEST_VERIFY_MSG(threadIdConsumer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
+
+				EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
+				
+				// Wait for producer to quit.
+				if(threadIdProducer != kThreadIdInvalid)
+				{
+					status = threadProducer.WaitForEnd(GetThreadTime() + 30000);
+					EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for producer end failed.");
+				}
+
+				EA::UnitTest::ThreadSleepRandom(2000, 2000);
+
+				// Wait for consumers to quit.
+				workData.mbConsumersShouldQuit = true;
+				if(threadIdConsumer != kThreadIdInvalid)
+				{
+					status = threadConsumer.WaitForEnd(GetThreadTime() + 30000);
+					EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for consumer end failed.");
+				}
+			}
+		}
+	#endif
+
+	return nErrorCount;
+}
+
+
+

+ 607 - 0
test/thread/source/TestThreadFutex.cpp

@@ -0,0 +1,607 @@
+///////////////////////////////////////////////////////////////////////////////
+// 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;
+}
+
+
+
+
+

+ 233 - 0
test/thread/source/TestThreadMutex.cpp

@@ -0,0 +1,233 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_mutex.h>
+#include <eathread/eathread_thread.h>
+#include <eathread/eathread_atomic.h>
+#include <stdlib.h>
+
+
+using namespace EA::Thread;
+
+
+const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+
+struct MWorkData
+{
+	volatile bool mbShouldQuit;
+	Mutex         mMutex;
+	volatile int  mnExpectedValue;
+	volatile int  mnCalculatedValue;
+	AtomicInt32   mnErrorCount;
+
+	MWorkData() : mbShouldQuit(false), mMutex(NULL, true), 
+				  mnExpectedValue(0), mnCalculatedValue(0), 
+				  mnErrorCount(0) {}
+
+	// Prevent default generation of these functions by not defining them
+private:
+	MWorkData(const MWorkData& rhs);               // copy constructor
+	MWorkData& operator=(const MWorkData& rhs);    // assignment operator
+};
+
+
+static intptr_t MutexTestThreadFunction(void* pvWorkData)
+{
+	int            nErrorCount      = 0;
+	MWorkData*     pWorkData        = (MWorkData*)pvWorkData;
+	const ThreadId threadId         = GetThreadId();
+	const int      currentProcessor = GetThreadProcessor();
+
+	EA::UnitTest::ReportVerbosity(1, "Mutex test function created: %s (currently on processor %d).\n", EAThreadThreadIdToString(threadId), currentProcessor);
+
+	while(!pWorkData->mbShouldQuit)
+	{
+		const int nRecursiveLockCount(rand() % 3);
+		int i, nLockResult, nLocks = 0;
+
+		for(i = 0; i < nRecursiveLockCount; i++)
+		{
+			// Do a lock but allow for the possibility of occasional timeout.
+			ThreadTime expectedTime(GetThreadTime() + 1000);
+			nLockResult = pWorkData->mMutex.Lock(expectedTime);
+
+			// Verify the lock succeeded or timed out.
+			EATEST_VERIFY_MSG(nLockResult != Mutex::kResultError, "Mutex failure.");
+
+			// Verify the timeout of the lock
+			if (nLockResult == Mutex::kResultTimeout)
+			{
+				ThreadTime currentTime(GetThreadTime());
+				EATEST_VERIFY_MSG(currentTime >= expectedTime, "Mutex timeout failure.");
+			}
+	 
+			if(nLockResult > 0) // If there was no timeout...
+			{
+				nLocks++;
+
+				// What we do here is spend some time manipulating mnExpectedValue and mnCalculatedValue
+				// while we have the lock. We change their values in a predicable way but before we 
+				// are done mnCalculatedValue has been incremented by one and both values are equal.
+				const uintptr_t x = (uintptr_t)pWorkData;
+
+				pWorkData->mnExpectedValue     = -1;
+				EA::UnitTest::ThreadSleepRandom(10, 20);
+				pWorkData->mnCalculatedValue *= 50;
+				EA::UnitTest::ThreadSleepRandom(10, 20);
+				pWorkData->mnCalculatedValue /= (int)(((x + 1) / x) * 50); // This will always be the same as simply '/= 50'.
+				EA::UnitTest::ThreadSleepRandom(10, 20);
+				pWorkData->mnCalculatedValue += 1;
+				EA::UnitTest::ThreadSleepRandom(10, 20);
+				pWorkData->mnExpectedValue     = pWorkData->mnCalculatedValue;
+
+				EATEST_VERIFY_MSG(pWorkData->mnCalculatedValue == pWorkData->mnExpectedValue, "Mutex failure.");
+			}
+			ThreadCooperativeYield(); // Used by cooperative threading platforms.
+		}
+
+		while(nLocks > 0)
+		{
+			// Verify that HasLock returns the expected value.
+			EATEST_VERIFY_MSG(pWorkData->mMutex.HasLock(), "Mutex failure.");
+
+			// Verify that N locks are set.
+			nLockResult = pWorkData->mMutex.GetLockCount();
+			EATEST_VERIFY_MSG(nLockResult == nLocks, "Mutex failure.");
+
+			// Verify the unlock result.
+			nLockResult = pWorkData->mMutex.Unlock();
+			EATEST_VERIFY_MSG(nLockResult >= nLocks-1, "Mutex failure.");
+
+			nLocks--;
+			ThreadCooperativeYield(); // Used by cooperative threading platforms.
+		}
+
+		// If EAT_ASSERT_ENABLED is 1, Mutex::HasLock() tests to see that that not only is the mutex 
+		// locked, but that the lock is owned by the calling thread.
+		#if EAT_ASSERT_ENABLED
+			// Verify that HasLock returns the expected value.
+			EATEST_VERIFY_MSG(!pWorkData->mMutex.HasLock(), "Mutex failure.");
+		#endif
+
+		EA::UnitTest::ThreadSleepRandom(100, 200);
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	return 0;
+}
+
+
+int TestThreadMutex()
+{
+	int nErrorCount(0);
+
+	{ // ctor tests
+		// We test various combinations of Mutex ctor and MutexParameters.
+		// MutexParameters(bool bIntraProcess = true, const char* pName = NULL);
+		// Mutex(const MutexParameters* pMutexParameters = NULL, bool bDefaultParameters = true);
+
+		MutexParameters mp1(true, NULL);
+		MutexParameters mp2(true, "mp2");
+		MutexParameters mp3(false, NULL);
+		MutexParameters mp4(false, "mp4WithNameThatIsMoreThan32Characters"); // testing kettle mutex name limitations
+
+		Mutex mutex1(&mp1, false);
+		Mutex mutex2(&mp2, false);
+		Mutex mutex3(&mp3, false);
+		Mutex mutex4(&mp4, false);
+		Mutex mutex5(NULL, true);
+		Mutex mutex6(NULL, false);
+		mutex6.Init(&mp1);
+
+		AutoMutex am1(mutex1);
+		AutoMutex am2(mutex2);
+		AutoMutex am3(mutex3);
+		AutoMutex am4(mutex4);
+		AutoMutex am5(mutex5);
+		AutoMutex am6(mutex6);
+	}
+
+	{ // Single-threaded tests
+		Mutex mutex(NULL, true);
+
+		int nLockCount;
+
+		nLockCount = mutex.GetLockCount();
+		EATEST_VERIFY_MSG(nLockCount == 0, "Mutex failure.");
+
+		nLockCount = mutex.Lock();
+		EATEST_VERIFY_MSG(nLockCount == 1, "Mutex failure.");
+
+		nLockCount = mutex.Lock();
+		EATEST_VERIFY_MSG(nLockCount == 2, "Mutex failure.");
+
+		nLockCount = mutex.Unlock();
+		EATEST_VERIFY_MSG(nLockCount == 1, "Mutex failure.");
+
+		nLockCount = mutex.GetLockCount();
+		EATEST_VERIFY_MSG(nLockCount == 1, "Mutex failure.");
+
+		nLockCount = mutex.Unlock();
+		EATEST_VERIFY_MSG(nLockCount == 0, "Mutex failure.");
+
+		nLockCount = mutex.Lock();
+		EATEST_VERIFY_MSG(nLockCount == 1, "Mutex failure.");
+
+		nLockCount = mutex.Unlock();
+		EATEST_VERIFY_MSG(nLockCount ==  0, "Mutex failure.");
+	}
+
+	#ifdef EA_PLATFORM_PS4
+	{
+		// Validate the amount of system resources being consumed by a Sony Mutex without a mutex name.  It appears the
+		// Sony OS allocates 32 bytes per mutex regardless if the mutex name is empty or not.  EAThread currently checks
+		// if the mutex name can be omited in an effort to save memory.
+		MutexParameters mp(false, nullptr); 
+
+		Mutex mutexes[4000];
+		for(auto& m : mutexes)
+			m.Init(&mp);
+	}
+	#endif
+
+	#if EA_THREADS_AVAILABLE
+
+		{  // Multithreaded test
+			MWorkData workData; 
+
+			const int kThreadCount(kMaxConcurrentThreadCount);
+			Thread thread[kThreadCount];
+			Thread::Status status;
+			int i;
+
+			for(i = 0; i < kThreadCount; i++)
+				thread[i].Begin(MutexTestThreadFunction, &workData);
+
+			EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*1000, gTestLengthSeconds*1000);
+
+			workData.mbShouldQuit = true;
+
+			for(i = 0; i < kThreadCount; i++)
+			{
+				status = thread[i].WaitForEnd(GetThreadTime() + 30000);
+
+				EATEST_VERIFY_MSG(status == EA::Thread::Thread::kStatusEnded, "Mutex/Thread failure: Thread(s) didn't end.");
+			}
+
+			nErrorCount += (int)workData.mnErrorCount;
+		}
+
+	#endif
+
+	return nErrorCount;
+}
+
+
+
+
+

+ 217 - 0
test/thread/source/TestThreadRWMutex.cpp

@@ -0,0 +1,217 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_rwmutex.h>
+#include <eathread/eathread_thread.h>
+#include <stdlib.h>
+
+
+using namespace EA::Thread;
+
+
+const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+
+struct RWMWorkData
+{
+	volatile bool   mbShouldQuit;
+	RWMutex         mRWMutex;
+	volatile int    mnExpectedValue;
+	volatile int    mnCalculatedValue;
+	AtomicInt32     mnErrorCount;
+
+	RWMWorkData() : mbShouldQuit(false), mRWMutex(NULL, true), mnExpectedValue(0), 
+					mnCalculatedValue(0), mnErrorCount(0) {}
+
+	// define copy ctor and assignment operator
+	// so the compiler does define them intrisically
+	RWMWorkData(const RWMWorkData& rhs);               // copy constructor
+	RWMWorkData& operator=(const RWMWorkData& rhs);    // assignment operator
+};
+
+
+static intptr_t ReaderFunction(void* pvWorkData)
+{
+	int          nErrorCount = 0;
+	RWMWorkData* pWorkData   = (RWMWorkData*)pvWorkData;
+	ThreadId     threadId    = GetThreadId();
+
+	EA::UnitTest::ReportVerbosity(1, "RWMutex reader test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	while(!pWorkData->mbShouldQuit)
+	{
+		const int nRecursiveLockCount(rand() % 3);
+		int i, nLockResult, nLocks = 0;
+
+		for(i = 0; i < nRecursiveLockCount; i++)
+		{
+			// Do a lock but allow for the possibility of occasional timeout.
+			nLockResult = pWorkData->mRWMutex.Lock(RWMutex::kLockTypeRead, GetThreadTime() + 20);
+
+			EATEST_VERIFY_MSG(nLockResult != RWMutex::kResultError, "RWMutex failure");
+
+			if(nLockResult > 0)
+			{
+				nLocks++;
+
+				EA::UnitTest::ReportVerbosity(2, "CValue = %d; EValue = %d\n", pWorkData->mnCalculatedValue, pWorkData->mnExpectedValue);
+				EATEST_VERIFY_MSG(pWorkData->mnCalculatedValue == pWorkData->mnExpectedValue, "RWMutex failure");
+			}
+			ThreadCooperativeYield(); // Used by cooperative threading platforms.
+		}
+
+		while(nLocks > 0)
+		{
+			// Verify no write locks are set.
+			nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeWrite);
+			EATEST_VERIFY_MSG(nLockResult == 0, "RWMutex failure");
+
+			// Verify at least N read locks are set.
+			nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeRead);
+			EATEST_VERIFY_MSG(nLockResult >= nLocks, "RWMutex failure");
+
+			// Verify there is one less read lock set.
+			nLockResult = pWorkData->mRWMutex.Unlock();
+			EATEST_VERIFY_MSG(nLockResult >= nLocks-1, "RWMutex failure");
+
+			nLocks--;
+			ThreadCooperativeYield(); // Used by cooperative threading platforms.
+		}
+
+		EA::UnitTest::ThreadSleepRandom(100, 200);
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	return 0;
+}
+
+
+static intptr_t WriterFunction(void* pvWorkData)
+{
+	int          nErrorCount = 0;
+	RWMWorkData* pWorkData   = (RWMWorkData*)pvWorkData;
+	ThreadId     threadId    = GetThreadId();
+
+	EA::UnitTest::ReportVerbosity(1, "RWMutex writer test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	while(!pWorkData->mbShouldQuit)
+	{
+		// Do a lock but allow for the possibility of occasional timeout.
+		int nLockResult = pWorkData->mRWMutex.Lock(RWMutex::kLockTypeWrite, GetThreadTime() + 30);
+		EATEST_VERIFY_MSG(nLockResult != RWMutex::kResultError, "RWMutex failure");
+
+		ThreadCooperativeYield(); // Used by cooperative threading platforms.
+
+		if(nLockResult > 0)
+		{
+			// Verify exactly one write lock is set.
+			nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeWrite);
+			EATEST_VERIFY_MSG(nLockResult == 1, "RWMutex failure");
+
+			// What we do here is spend some time manipulating mnExpectedValue and mnCalculatedValue
+			// while we have the write lock. We change their values in a predicable way but before 
+			// we are done mnCalculatedValue has been incremented by one and both values are equal.
+			const uintptr_t x = (uintptr_t)pWorkData;
+
+			pWorkData->mnExpectedValue     = -1;
+			EA::UnitTest::ThreadSleepRandom(10, 20);
+			pWorkData->mnCalculatedValue *= 50;
+			EA::UnitTest::ThreadSleepRandom(10, 20);
+			pWorkData->mnCalculatedValue /= (int)(((x + 1) / x) * 50); // This will always be the same as simply '/= 50'.
+			EA::UnitTest::ThreadSleepRandom(10, 20);
+			pWorkData->mnCalculatedValue += 1;
+			EA::UnitTest::ThreadSleepRandom(10, 20);
+			pWorkData->mnExpectedValue     = pWorkData->mnCalculatedValue;
+
+			// Verify no read locks are set.
+			nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeRead);
+			EATEST_VERIFY_MSG(nLockResult == 0, "RWMutex failure");
+
+			// Verify exactly one write lock is set.
+			nLockResult = pWorkData->mRWMutex.GetLockCount(RWMutex::kLockTypeWrite);
+			EATEST_VERIFY_MSG(nLockResult == 1, "RWMutex failure");
+
+			// Verify there are now zero write locks set.
+			nLockResult = pWorkData->mRWMutex.Unlock();
+			EATEST_VERIFY_MSG(nLockResult == 0, "RWMutex failure");
+
+			EA::UnitTest::ThreadSleepRandom(400, 800);
+		}
+	}
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	return 0;
+}
+
+
+int TestThreadRWMutex()
+{
+	int nErrorCount(0);
+
+	// Be careful adding when adding more mutexes to this test as the the CTR runs out of 
+	// resources pretty early.
+	{
+		RWMutexParameters mp1(true, NULL);
+		RWMutexParameters mp2(true, "mp2");
+
+		{
+			RWMutex mutex1(&mp1, false);
+			RWMutex mutex2(&mp2, false);
+		}
+
+		{
+			RWMutex mutex5(NULL, true);
+			RWMutex mutex6(NULL, false);
+			mutex6.Init(&mp1);
+		}
+
+		{
+			RWMutex mutex1(&mp1, false);
+			AutoRWMutex am1(mutex1, RWMutex::kLockTypeRead);
+		}
+	}
+
+	#if EA_THREADS_AVAILABLE
+
+		{
+			RWMWorkData workData; 
+
+			const int      kThreadCount(kMaxConcurrentThreadCount - 1);
+			Thread         thread[kThreadCount];
+			ThreadId       threadId[kThreadCount];
+			Thread::Status status;
+
+			for(int i(0); i < kThreadCount; i++)
+			{
+				if(i < (kThreadCount * 3 / 4))
+					threadId[i] = thread[i].Begin(ReaderFunction, &workData);
+				else
+					threadId[i] = thread[i].Begin(WriterFunction, &workData);
+			}
+
+			EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
+
+			workData.mbShouldQuit = true;
+
+			for(int i(0); i < kThreadCount; i++)
+			{
+				if(threadId[i] != kThreadIdInvalid)
+				{
+					status = thread[i].WaitForEnd(GetThreadTime() + 30000);
+					EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWMutex/Thread failure: status == kStatusRunning.");
+				}
+			}
+
+			nErrorCount += (int)workData.mnErrorCount;
+		}
+
+	#endif
+
+	return nErrorCount;
+}
+

+ 259 - 0
test/thread/source/TestThreadRWSemaLock.cpp

@@ -0,0 +1,259 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_thread.h>
+#include <eathread/eathread_rwsemalock.h>
+#include <stdlib.h>
+
+
+
+const int kThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// RWSTestType
+//
+enum RWSTestType
+{
+	kRWSTestTypeStandard,
+	kRWSTestTypeAllWriters,
+	kRWSTestTypeAllReaders,
+	kRWSTestTypeMostlyWriters,
+	kRWSTestTypeMostlyReaders,
+	kRWSTestTypeCount
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// RWSemaWorkData
+//
+struct RWSemaWorkData
+{
+	volatile bool               mbShouldQuit;
+	EA::Thread::RWSemaLock      mRWSemaLock;
+	volatile int                mnWriterCount;
+	EA::Thread::AtomicInt32     mnErrorCount;
+	EA::Thread::AtomicInt32     mnCurrentTestType;
+
+	RWSemaWorkData()
+		: mbShouldQuit(false)
+		, mRWSemaLock()
+		, mnWriterCount(0)
+		, mnErrorCount(0)
+		, mnCurrentTestType(kRWSTestTypeStandard) {}
+
+private:
+	RWSemaWorkData(const RWSemaWorkData& rhs);
+	RWSemaWorkData& operator=(const RWSemaWorkData& rhs);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// RWSThreadFunction
+//
+static intptr_t RWSThreadFunction(void* pvWorkData)
+{
+	using namespace EA::Thread;
+
+	RWSemaWorkData* const pWorkData = (RWSemaWorkData*)pvWorkData;
+
+	ThreadId threadId = GetThreadId();
+	EA::UnitTest::ReportVerbosity(1, "RWSemaLock test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	int nErrorCount = 0;
+
+	while(!pWorkData->mbShouldQuit)
+	{
+		int               nWriteLockChance = 0;
+		const RWSTestType testType         = (RWSTestType)pWorkData->mnCurrentTestType.GetValue();
+
+		switch (testType)
+		{
+			default:
+			case kRWSTestTypeStandard:
+				nWriteLockChance = 20;
+				break;
+
+			case kRWSTestTypeAllWriters:
+				nWriteLockChance = 1000;
+				break;
+
+			case kRWSTestTypeAllReaders:
+				nWriteLockChance = 0;
+				break;
+
+			case kRWSTestTypeMostlyWriters:
+				nWriteLockChance = 700;
+				break;
+
+			case kRWSTestTypeMostlyReaders:
+				nWriteLockChance = 5;
+				break;
+		}
+
+		const bool bShouldWrite = ((rand() % 1000) < nWriteLockChance);
+
+		if(bShouldWrite)
+		{
+			AutoSemaWriteLock _(pWorkData->mRWSemaLock);
+			pWorkData->mnWriterCount++;
+			EA::UnitTest::ThreadSleepRandom(2, 10);
+			pWorkData->mnWriterCount--;
+		}
+		else
+		{
+			AutoSemaReadLock _(pWorkData->mRWSemaLock);
+			EATEST_VERIFY_MSG(pWorkData->mnWriterCount == 0, "ReadLock is held, there should be no active WriteLocks.");
+		}
+	}
+
+	pWorkData->mnErrorCount.SetValue(nErrorCount);
+
+	return nErrorCount;
+}
+
+
+// NOTE(rparolin):
+// This exists to introduce test-only functionality for the RWSemaLock.  We can add these functions here because we
+// guarantee they will not be called in a concurrent context and they simplify validation of assumption of the lock.
+struct TestRWSemaLock : public EA::Thread::RWSemaLock
+{
+	TestRWSemaLock() = default;
+	TestRWSemaLock(const TestRWSemaLock&) = delete;
+	TestRWSemaLock(TestRWSemaLock&&) = delete;
+	TestRWSemaLock& operator=(const TestRWSemaLock&) = delete;
+	TestRWSemaLock& operator=(TestRWSemaLock&&) = delete;
+
+	bool IsReadLocked()
+	{
+		Status status;
+		status.data = mStatus.GetValue();
+		return status.readers > 0;
+	}
+
+	bool IsWriteLocked()
+	{
+		Status status;
+		status.data = mStatus.GetValue();
+		return status.writers > 0;
+	}
+};
+
+
+int TestThreadRWSemaLock()
+{
+	using namespace EA::Thread;
+
+	int nErrorCount = 0;
+
+	{ // RWSemaLock -- Basic single-threaded test.
+
+		TestRWSemaLock rwSemaLock; // There are no construction parameters.
+
+		EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(),  "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure");
+
+		rwSemaLock.ReadTryLock();
+		EATEST_VERIFY_MSG(rwSemaLock.IsReadLocked(),   "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.WriteTryLock(),  "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure");
+
+		rwSemaLock.ReadLock();
+		EATEST_VERIFY_MSG(rwSemaLock.IsReadLocked(), "RWSemaLock failure");
+
+		rwSemaLock.ReadUnlock();
+		EATEST_VERIFY_MSG(rwSemaLock.IsReadLocked(), "RWSemaLock failure");
+
+		rwSemaLock.ReadUnlock();
+		EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure");
+
+		rwSemaLock.WriteTryLock();
+		EATEST_VERIFY_MSG(rwSemaLock.IsWriteLocked(), "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.ReadTryLock(),  "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.WriteTryLock(), "RWSemaLock failure");
+	}
+
+
+	{ // AutoRWSemaLock -- Basic single-threaded test.
+		TestRWSemaLock rwSemaLock; // There are no construction parameters.
+
+		{  //Special scope just for the AutoRWSemaLock
+			AutoSemaReadLock autoRWSemaLock1(rwSemaLock);
+			AutoSemaReadLock autoRWSemaLock2(rwSemaLock);
+
+			EATEST_VERIFY_MSG(rwSemaLock.IsReadLocked(),   "RWSemaLock failure");
+			EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure");
+			EATEST_VERIFY_MSG(!rwSemaLock.WriteTryLock(),  "RWSemaLock failure");
+			EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure");
+		}
+
+		EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(),  "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure");
+
+		{  //Special scope just for the AutoRWSemaLock
+			AutoSemaWriteLock autoRWSemaLock(rwSemaLock);
+
+			EATEST_VERIFY_MSG(rwSemaLock.IsWriteLocked(), "RWSemaLock failure");
+			EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure");
+			EATEST_VERIFY_MSG(!rwSemaLock.ReadTryLock(),  "RWSemaLock failure");
+			EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(), "RWSemaLock failure");
+		}
+
+		EATEST_VERIFY_MSG(!rwSemaLock.IsReadLocked(),  "RWSemaLock failure");
+		EATEST_VERIFY_MSG(!rwSemaLock.IsWriteLocked(), "RWSemaLock failure");
+	}
+
+
+	#if EA_THREADS_AVAILABLE
+
+		{  // Multithreaded test
+			  
+			RWSemaWorkData workData; 
+
+			Thread         thread[kThreadCount];
+			ThreadId       threadId[kThreadCount];
+			Thread::Status status;
+
+			for(int i(0); i < kThreadCount; i++)
+				threadId[i] = thread[i].Begin(RWSThreadFunction, &workData);
+
+			for(int e = 0; e < kRWSTestTypeCount; e++)
+			{
+				workData.mnCurrentTestType.SetValue(e);
+				EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 500, gTestLengthSeconds * 500);
+			}
+
+			workData.mbShouldQuit = true;
+			for(int t(0); t < kThreadCount; t++)
+			{
+				if(threadId[t] != kThreadIdInvalid)
+				{
+					status = thread[t].WaitForEnd(GetThreadTime() + 30000);
+					EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWSemalock/Thread failure: status == kStatusRunning.\n");
+				}
+			}
+
+			nErrorCount += (int)workData.mnErrorCount;
+		}
+
+	#endif
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+
+

+ 477 - 0
test/thread/source/TestThreadRWSpinLock.cpp

@@ -0,0 +1,477 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_thread.h>
+#include <eathread/eathread_rwspinlock.h>
+#include <eathread/eathread_rwspinlockw.h>
+#include <stdlib.h>
+#include <atomic>
+
+
+const int kThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// RWSTestType
+//
+enum RWSTestType
+{
+	kRWSTestTypeStandard,
+	kRWSTestTypeAllWriters,
+	kRWSTestTypeAllReaders,
+	kRWSTestTypeMostlyWriters,
+	kRWSTestTypeMostlyReaders,
+	kRWSTestTypeCount
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// RWSWorkData
+//
+struct RWSWorkData
+{
+	std::atomic<bool>           mbShouldQuit;
+	EA::Thread::RWSpinLock      mRWSpinLock;
+	EA::Thread::RWSpinLockW     mRWSpinLockW;
+	volatile int                mnExpectedValue;
+	volatile int                mnCalculatedValue;
+	EA::Thread::AtomicInt32     mnErrorCount;
+	EA::Thread::AtomicInt32     mnCurrentTestType;
+
+	RWSWorkData() : mbShouldQuit(false), mRWSpinLock(), mRWSpinLockW(), mnExpectedValue(0), mnCalculatedValue(0), 
+					mnErrorCount(0), mnCurrentTestType(kRWSTestTypeStandard) {}
+
+private:
+	RWSWorkData(const RWSWorkData& rhs);
+	RWSWorkData& operator=(const RWSWorkData& rhs);
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// RWSWThreadFunction
+//
+static intptr_t RWSWThreadFunction(void* pvWorkData)
+{
+	using namespace EA::Thread;
+
+	RWSWorkData* const pWorkData = (RWSWorkData*)pvWorkData;
+
+	ThreadId threadId = GetThreadId();
+	EA::UnitTest::ReportVerbosity(1, "RWSpinLockW test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	int nErrorCount = 0;
+
+	while(!pWorkData->mbShouldQuit)
+	{
+		int               nWriteLockChance = 0;
+		const RWSTestType testType         = (RWSTestType)pWorkData->mnCurrentTestType.GetValue();
+
+		switch (testType)
+		{
+			default:
+			case kRWSTestTypeStandard:
+				nWriteLockChance = 20;
+				break;
+
+			case kRWSTestTypeAllWriters:
+				nWriteLockChance = 1000;
+				break;
+
+			case kRWSTestTypeAllReaders:
+				nWriteLockChance = 0;
+				break;
+
+			case kRWSTestTypeMostlyWriters:
+				nWriteLockChance = 700;
+				break;
+
+			case kRWSTestTypeMostlyReaders:
+				nWriteLockChance = 5;
+				break;
+		}
+
+		const bool bShouldWrite = ((rand() % 1000) < nWriteLockChance);
+
+		if(bShouldWrite)
+		{
+			pWorkData->mRWSpinLockW.WriteLock();
+
+			EATEST_VERIFY_MSG(!pWorkData->mRWSpinLockW.IsReadLocked(), "RWSpinlock failure: IsReadLocked\n");
+			EATEST_VERIFY_MSG(pWorkData->mRWSpinLockW.IsWriteLocked(), "RWSpinlock failure: IsWriteLocked\n");
+
+			pWorkData->mRWSpinLockW.WriteUnlock();
+
+			ThreadCooperativeYield();
+		}
+		else
+		{
+			const int nRecursiveLockCount = 1; // Disabled because we are not recursive: (rand() % 10) ? 1 : 2;
+
+			int nLocks = 0;
+
+			for(int i = 0; i < nRecursiveLockCount; i++)
+			{
+				pWorkData->mRWSpinLockW.ReadLock();
+				nLocks++;
+
+				ThreadCooperativeYield();
+			}
+
+			// Disabled because we are not recursive:
+			// if((rand() % 10) == 0)
+			// {
+			//     if(pWorkData->mRWSpinLockW.ReadTryLock())
+			//         nLocks++;
+			// }
+
+			while(nLocks > 0)
+			{
+				EATEST_VERIFY_MSG(pWorkData->mRWSpinLockW.IsReadLocked(),   "RWSpinlock failure: IsReadLocked\n");
+				EATEST_VERIFY_MSG(!pWorkData->mRWSpinLockW.IsWriteLocked(), "RWSpinlock failure: IsWriteLocked\n");
+
+				pWorkData->mRWSpinLockW.ReadUnlock();
+				nLocks--;
+
+				ThreadCooperativeYield();
+			}
+		}
+
+		//if((rand() % 1000) < 3)
+		//    EA::UnitTest::ThreadSleepRandom(50, 100);
+	}
+
+	pWorkData->mnErrorCount.SetValue(nErrorCount);
+
+	return nErrorCount;
+}
+
+
+
+static int TestThreadRWSpinLockW()
+{
+	using namespace EA::Thread;
+
+	int nErrorCount = 0;
+
+	{ // RWSpinLockW -- Basic single-threaded test.
+
+		RWSpinLockW rwSpinLock; // There are no construction parameters.
+
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(),  "RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure");
+
+		rwSpinLock.ReadTryLock();
+		EATEST_VERIFY_MSG( rwSpinLock.IsReadLocked(),  "RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(),  "RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure");
+
+		// Disabled because we don't support read lock recursion.
+		// rwSpinLock.ReadLock();
+		// EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLockW failure");
+
+		// rwSpinLock.ReadUnlock();
+		// EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLockW failure");
+
+		rwSpinLock.ReadUnlock();
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure");
+
+		rwSpinLock.WriteTryLock();
+		EATEST_VERIFY_MSG( rwSpinLock.IsWriteLocked(),"RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.ReadTryLock(),  "RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(), "RWSpinLockW failure");
+	}
+
+
+	{ // AutoRWSpinLockW -- Basic single-threaded test.
+		RWSpinLockW rwSpinLock; // There are no construction parameters.
+
+		{  //Special scope just for the AutoRWSpinLockW
+			AutoRWSpinLockW autoRWSpinLockW1(rwSpinLock, AutoRWSpinLockW::kLockTypeRead);
+			AutoRWSpinLockW autoRWSpinLockW2(rwSpinLock, AutoRWSpinLockW::kLockTypeRead);
+
+			EATEST_VERIFY_MSG( rwSpinLock.IsReadLocked(),  "RWSpinLockW failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(),  "RWSpinLockW failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure");
+		}
+
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(),  "RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure");
+
+		{  //Special scope just for the AutoRWSpinLockW
+			AutoRWSpinLockW autoRWSpinLockW(rwSpinLock, AutoRWSpinLockW::kLockTypeWrite);
+
+			EATEST_VERIFY_MSG( rwSpinLock.IsWriteLocked(),"RWSpinLockW failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.ReadTryLock(),  "RWSpinLockW failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLockW failure");
+		}
+
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(),  "RWSpinLockW failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLockW failure");
+	}
+
+
+	#if EA_THREADS_AVAILABLE
+
+		{  // Multithreaded test
+			  
+			RWSWorkData workData; 
+
+			Thread         thread[kThreadCount];
+			ThreadId       threadId[kThreadCount];
+			Thread::Status status;
+
+			for(int i(0); i < kThreadCount; i++)
+				threadId[i] = thread[i].Begin(RWSWThreadFunction, &workData);
+
+			for(int e = 0; e < kRWSTestTypeCount; e++)
+			{
+				workData.mnCurrentTestType.SetValue(e);
+				EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 500, gTestLengthSeconds * 500);
+			}
+
+			workData.mbShouldQuit = true;
+
+			for(int t(0); t < kThreadCount; t++)
+			{
+				if(threadId[t] != kThreadIdInvalid)
+				{
+					status = thread[t].WaitForEnd(GetThreadTime() + 30000);
+					EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWSpinlock/Thread failure: status == kStatusRunning.\n");
+				}
+			}
+
+			nErrorCount += (int)workData.mnErrorCount;
+		}
+
+	#endif
+
+	return nErrorCount;
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// RWSThreadFunction
+//
+static intptr_t RWSThreadFunction(void* pvWorkData)
+{
+	using namespace EA::Thread;
+
+	RWSWorkData* const pWorkData = (RWSWorkData*)pvWorkData;
+
+	ThreadId threadId = GetThreadId();
+	EA::UnitTest::ReportVerbosity(1, "RWSpinLock test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	int nErrorCount = 0;
+
+	while(!pWorkData->mbShouldQuit)
+	{
+		int               nWriteLockChance = 0;
+		const RWSTestType testType         = (RWSTestType)pWorkData->mnCurrentTestType.GetValue();
+
+		switch (testType)
+		{
+			default:
+			case kRWSTestTypeStandard:
+				nWriteLockChance = 20;
+				break;
+
+			case kRWSTestTypeAllWriters:
+				nWriteLockChance = 1000;
+				break;
+
+			case kRWSTestTypeAllReaders:
+				nWriteLockChance = 0;
+				break;
+
+			case kRWSTestTypeMostlyWriters:
+				nWriteLockChance = 700;
+				break;
+
+			case kRWSTestTypeMostlyReaders:
+				nWriteLockChance = 5;
+				break;
+		}
+
+		const bool bShouldWrite = ((rand() % 1000) < nWriteLockChance);
+
+		if(bShouldWrite)
+		{
+			pWorkData->mRWSpinLock.WriteLock();
+
+			EATEST_VERIFY_MSG(!pWorkData->mRWSpinLock.IsReadLocked(), "RWSpinlock failure: IsReadLocked\n");
+			EATEST_VERIFY_MSG(pWorkData->mRWSpinLock.IsWriteLocked(), "RWSpinlock failure: IsWriteLocked\n");
+
+			pWorkData->mRWSpinLock.WriteUnlock();
+
+			ThreadCooperativeYield();
+		}
+		else
+		{
+			const int nRecursiveLockCount = (rand() % 10) ? 1 : 2;
+
+			int nLocks = 0;
+
+			for(int i = 0; i < nRecursiveLockCount; i++)
+			{
+				pWorkData->mRWSpinLock.ReadLock();
+				nLocks++;
+
+				ThreadCooperativeYield();
+			}
+
+			if((rand() % 10) == 0)
+			{
+				if(pWorkData->mRWSpinLock.ReadTryLock())
+					nLocks++;
+			}
+
+			while(nLocks > 0)
+			{
+				const int32_t n = pWorkData->mRWSpinLock.mValue; (void)n;
+
+				// It turns out IsReadLocked has a bug and can return a false negative. 
+				// EATEST_VERIFY_MSG(pWorkData->mRWSpinLock.IsReadLocked(), "RWSpinlock failure: IsReadLocked\n");
+				// EATEST_VERIFY_MSG(!pWorkData->mRWSpinLock.IsWriteLocked(), "RWSpinlock failure: IsWriteLocked\n");
+
+				pWorkData->mRWSpinLock.ReadUnlock();
+				nLocks--;
+
+				ThreadCooperativeYield();
+			}
+		}
+	}
+
+	pWorkData->mnErrorCount.SetValue(nErrorCount);
+
+	return nErrorCount;
+}
+
+
+
+int TestThreadRWSpinLock()
+{
+	using namespace EA::Thread;
+
+	int nErrorCount = 0;
+
+	{ // RWSpinLock -- Basic single-threaded test.
+
+		RWSpinLock rwSpinLock; // There are no construction parameters.
+
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(),  "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure");
+
+		rwSpinLock.ReadTryLock();
+		EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(),   "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(),  "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure");
+
+		rwSpinLock.ReadLock();
+		EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLock failure");
+
+		rwSpinLock.ReadUnlock();
+		EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(), "RWSpinLock failure");
+
+		rwSpinLock.ReadUnlock();
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure");
+
+		rwSpinLock.WriteTryLock();
+		EATEST_VERIFY_MSG(rwSpinLock.IsWriteLocked(), "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.ReadTryLock(),  "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(), "RWSpinLock failure");
+	}
+
+
+	{ // AutoRWSpinLock -- Basic single-threaded test.
+		RWSpinLock rwSpinLock; // There are no construction parameters.
+
+		{  //Special scope just for the AutoRWSpinLock
+			AutoRWSpinLock autoRWSpinLock1(rwSpinLock, AutoRWSpinLock::kLockTypeRead);
+			AutoRWSpinLock autoRWSpinLock2(rwSpinLock, AutoRWSpinLock::kLockTypeRead);
+
+			EATEST_VERIFY_MSG(rwSpinLock.IsReadLocked(),   "RWSpinLock failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.WriteTryLock(),  "RWSpinLock failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure");
+		}
+
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(),  "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure");
+
+		{  //Special scope just for the AutoRWSpinLock
+			AutoRWSpinLock autoRWSpinLock(rwSpinLock, AutoRWSpinLock::kLockTypeWrite);
+
+			EATEST_VERIFY_MSG(rwSpinLock.IsWriteLocked(), "RWSpinLock failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.ReadTryLock(),  "RWSpinLock failure");
+			EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(), "RWSpinLock failure");
+		}
+
+		EATEST_VERIFY_MSG(!rwSpinLock.IsReadLocked(),  "RWSpinLock failure");
+		EATEST_VERIFY_MSG(!rwSpinLock.IsWriteLocked(), "RWSpinLock failure");
+	}
+
+
+	#if EA_THREADS_AVAILABLE
+
+		{  // Multithreaded test
+			  
+			RWSWorkData workData; 
+
+			Thread         thread[kThreadCount];
+			ThreadId       threadId[kThreadCount];
+			Thread::Status status;
+
+			for(int i(0); i < kThreadCount; i++)
+				threadId[i] = thread[i].Begin(RWSThreadFunction, &workData);
+
+			for(int e = 0; e < kRWSTestTypeCount; e++)
+			{
+				workData.mnCurrentTestType.SetValue(e);
+				EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 500, gTestLengthSeconds * 500);
+			}
+
+			workData.mbShouldQuit = true;
+
+			for(int t(0); t < kThreadCount; t++)
+			{
+				if(threadId[t] != kThreadIdInvalid)
+				{
+					status = thread[t].WaitForEnd(GetThreadTime() + 30000);
+					EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "RWSpinlock/Thread failure: status == kStatusRunning.\n");
+				}
+			}
+
+			nErrorCount += (int)workData.mnErrorCount;
+		}
+
+	#endif
+
+	// TestThreadRWSpinLockW
+	nErrorCount += TestThreadRWSpinLockW();
+
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+
+

+ 353 - 0
test/thread/source/TestThreadSemaphore.cpp

@@ -0,0 +1,353 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <EAStdC/EAStopwatch.h>
+#include <eathread/eathread.h>
+#include <eathread/eathread_semaphore.h>
+#include <eathread/eathread_thread.h>
+#include <eathread/eathread_atomic.h>
+
+
+using namespace EA::Thread;
+
+
+const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+
+struct SWorkData
+{
+	volatile bool   mbShouldQuit;
+	Semaphore       mSemaphore;
+	int             mnPostCount;        // The count to use for Semaphore::Post()
+	AtomicInt32     mnExpectedCount;
+	AtomicInt32     mnThreadCount;
+	AtomicInt32     mnErrorCount;
+
+	SWorkData(const SemaphoreParameters& sp)
+	  : mbShouldQuit(false), mSemaphore(&sp, false), 
+		mnPostCount(1), mnExpectedCount(0), mnThreadCount(0), 
+		mnErrorCount(0) {}
+
+	// define copy ctor and assignment operator
+	// so the compiler does define them intrisically
+	SWorkData(const SWorkData& rhs);                  // copy constructor
+	SWorkData& operator=(const SWorkData& rhs);    // assignment operator
+};
+
+
+
+static intptr_t SemaphoreTestThreadFunction(void* pvWorkData)
+{
+	SWorkData* pWorkData   = (SWorkData*)pvWorkData;
+	int        nErrorCount = 0;
+	ThreadId   threadId    = GetThreadId();
+
+	EA::UnitTest::ReportVerbosity(1, "Semaphore test function created: %s\n", EAThreadThreadIdToString(threadId));
+
+	const AtomicInt32::ValueType nThreadCount = pWorkData->mnThreadCount++; // AtomicInt32 operation.
+	const AtomicInt32::ValueType nPostCount   = pWorkData->mnPostCount;
+
+	#if defined(EA_PLATFORM_DESKTOP)    // If the platform tends to run on fast hardware...
+		const unsigned kShortSleepMin = 50;
+		const unsigned kShortSleepMax = 100;
+	#else
+		const unsigned kShortSleepMin = 100;
+		const unsigned kShortSleepMax = 200;
+	#endif
+
+	while(!pWorkData->mbShouldQuit)
+	{
+		if((nThreadCount % 2) == 0) // If the first thread or the third thread...
+		{
+			// Create 'work'.
+			pWorkData->mnExpectedCount += nPostCount; // Atomic operation.
+			pWorkData->mSemaphore.Post(nPostCount);
+			EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax);
+
+			// Get the amount of 'work' that is expected.
+			int nWaitCount = nPostCount;
+
+			// Wait for the 'work' to be queued and signalled.
+			while(nWaitCount-- > 0)
+			{
+				const int result = pWorkData->mSemaphore.Wait(GetThreadTime() + 2000);
+
+				EATEST_VERIFY_MSG((result >= 0) || (result == Semaphore::kResultTimeout), "Semaphore failure 4a: semaphore.Wait()\n");
+
+				if(result >= 0) // If we we able to acquire the semaphore...
+					--pWorkData->mnExpectedCount; // Atomic operation.
+				// else timeout occurred. Every time we have this timeout we miss decrementing the semaphore by one, and thus the expected semaphore count rises by one. It doesn't mean there's a bug, it just means the expected count migrates higher as we get timeouts.
+
+				EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax);
+			}
+		}
+		else
+		{
+			// Get the amount of 'work' that is expected.
+			int nWaitCount = nPostCount;
+
+			// Wait for the 'work' to be queued and signalled.
+			while(nWaitCount-- > 0)
+			{
+				const int result = pWorkData->mSemaphore.Wait(GetThreadTime() + 2000);
+
+				EATEST_VERIFY_MSG((result >= 0) || (result == Semaphore::kResultTimeout), "Semaphore failure 4b: semaphore.Wait()\n");
+
+				if(result >= 0) // If we we able to acquire the semaphore...
+					--pWorkData->mnExpectedCount; // Atomic operation.
+				// else timeout occurred. Every time we have this timeout we miss decrementing the semaphore by one, and thus the expected semaphore count rises by one. It doesn't mean there's a bug, it just means the expected count migrates higher as we get timeouts.
+
+				EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax);
+			}
+
+			// Create 'work'.
+			pWorkData->mnExpectedCount += nPostCount; // Atomic operation.
+			pWorkData->mSemaphore.Post(nPostCount);
+			EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax);
+		}
+
+		EATEST_VERIFY_MSG(pWorkData->mSemaphore.GetCount() >= 0, "Semaphore failure 4c: The count should always be >= 0.\n");
+
+		// We restrict this assert to desktop platforms because the expected value migrates upward on every Wait timeout, and on slower platforms there could be a lot of such timeouts.
+		#if defined(EA_PLATFORM_DESKTOP)
+			EATEST_VERIFY_MSG(pWorkData->mSemaphore.GetCount() < 200, "Semaphore failure 4d: The count should always be a small value.\n");
+		#endif
+	}
+
+	pWorkData->mnErrorCount = nErrorCount;
+
+	return 0;
+}
+
+
+
+struct BadSignalTestData
+{
+	 EA::Thread::Semaphore  mSemaphore;             // The Semaphore that we are using.
+	 AtomicInt32            mnOKWaitCount;          // The number of Wait timeouts that occurred.
+	 AtomicInt32            mnTimeoutWaitCount;     // The number of Wait OK returns that occurred.
+	 AtomicInt32            mnErrorWaitCount;       // The number of Wait error returns that occurred.
+	 int                    mnWaitTimeout;          // How long a waiter waits before timing out.
+	 int                    mnWaitThreadCount;      // How many waiter threads there are. Caller sets up this value.
+	 int                    mnPostThreadCount;      // How many poster threads there are. Caller sets up this value.
+	 int                    mnPostCount;            // How many signals the poster should Post. Needs to be less than mnWaitThreadCount for this test. Caller sets up this value.
+	 int                    mnPostDelay;            // How long the poster waits before Posting. Needs to be significantly less than mnWaitTimeout.
+
+	 BadSignalTestData()
+	  : mSemaphore(), mnOKWaitCount(0), mnTimeoutWaitCount(0), mnErrorWaitCount(0),
+		mnWaitTimeout(0), mnWaitThreadCount(0), mnPostThreadCount(0),
+		mnPostCount(0), mnPostDelay(0) { }
+
+	// Define copy ctor and assignment operator
+	// so the compiler does define them intrisically
+	BadSignalTestData(const BadSignalTestData& rhs);               // copy constructor
+	BadSignalTestData& operator=(const BadSignalTestData& rhs);    // assignment operator
+};
+
+
+static intptr_t BadSignalTestWaitFunction(void *data)
+{
+	BadSignalTestData* const pBSTD = (BadSignalTestData*)data;
+
+	const int waitResult = pBSTD->mSemaphore.Wait(EA::Thread::GetThreadTime() + pBSTD->mnWaitTimeout);
+
+	if(waitResult == Semaphore::kResultTimeout)
+		pBSTD->mnTimeoutWaitCount.Increment();
+	else if(waitResult >= 0)
+		pBSTD->mnOKWaitCount.Increment();
+	else
+		pBSTD->mnErrorWaitCount.Increment();
+
+	return 0;
+}
+
+
+static intptr_t BadSignalTestPostFunction(void *data)
+{
+	BadSignalTestData* const pBSTD = (BadSignalTestData*)data;
+
+	ThreadSleep((ThreadTime) pBSTD->mnPostDelay);
+	pBSTD->mSemaphore.Post(pBSTD->mnPostCount); // Intentionally post less than the number of waiting threads.
+
+	return 0;
+}
+
+
+
+
+int TestThreadSemaphore()
+{
+	int nErrorCount(0);
+
+	{ // Single threaded test.
+		const int kInitialCount(4);
+		int nResult;
+
+		SemaphoreParameters sp(kInitialCount, false, "SingleThreadTest");
+		Semaphore semaphore(&sp);
+
+		nResult = semaphore.GetCount();
+		EATEST_VERIFY_F(nResult == kInitialCount, "Semaphore failure 2a: semaphore.GetCount(). result = %d\n", nResult);
+
+		// This triggers an assert and so cannot be tested here:
+		// bResult = semaphore.SetCount(10);
+		// EATEST_VERIFY_MSG(!bResult, "Semaphore failure in semaphore.SetCount(10)\n");
+
+		// Grab the full count, leaving none.
+		for(int i(0); i < kInitialCount; i++)
+		{
+			nResult = semaphore.Wait(kTimeoutNone);
+			EATEST_VERIFY_F(nResult == kInitialCount - i - 1, "Semaphore failure 2b: semaphore.Wait(kTimeoutNone). result = %d\n", nResult);
+
+			nResult = semaphore.GetCount();
+			EATEST_VERIFY_F(nResult == kInitialCount - i - 1, "Semaphore failure 2c: semaphore.GetCount(). result = %d\n", nResult);
+		}
+
+		nResult = semaphore.Wait(kTimeoutImmediate);
+		EATEST_VERIFY_F(nResult == Semaphore::kResultTimeout, "Semaphore failure 2d: semaphore.Wait(kTimeoutImmediate). result = %d\n", nResult);
+
+		nResult = semaphore.GetCount();
+		EATEST_VERIFY_F(nResult == 0, "Semaphore failure 2e: semaphore.GetCount(). result = %d\n", nResult);
+
+		nResult = semaphore.Post(2);
+		EATEST_VERIFY_F(nResult == 2, "Semaphore failure 2f: semaphore.Post(2). result = %d\n", nResult);
+
+		nResult = semaphore.Post(2);
+		EATEST_VERIFY_F(nResult == 4, "Semaphore failure 2g: semaphore.Post(2). result = %d\n", nResult);
+	}
+
+	
+	#if EA_THREADS_AVAILABLE
+
+		{   // Bad signal test.
+			BadSignalTestData bstd;
+			Thread            thread[4];
+
+			// These values need to be set up right or else this test won't work. 
+			// What we are trying to do here is make certain that N number of Semaphore
+			// waiters time out, while M waiters succeed.
+			bstd.mnWaitTimeout     = 5000;
+			bstd.mnPostDelay       = 2000;
+			bstd.mnWaitThreadCount = 3;      // mnWaitThreadCount + mnPostThreadCount should be [4].
+			bstd.mnPostThreadCount = 1;
+			bstd.mnPostCount       = 1;
+
+			thread[0].Begin(BadSignalTestWaitFunction, &bstd);
+			thread[1].Begin(BadSignalTestWaitFunction, &bstd);
+			thread[2].Begin(BadSignalTestWaitFunction, &bstd);
+			thread[3].Begin(BadSignalTestPostFunction, &bstd);
+
+			// Wait for the threads to be completed
+			thread[0].WaitForEnd();
+			thread[1].WaitForEnd();
+			thread[2].WaitForEnd();
+			thread[3].WaitForEnd();
+
+			EATEST_VERIFY_MSG(bstd.mnTimeoutWaitCount == (bstd.mnWaitThreadCount - (bstd.mnPostThreadCount * bstd.mnPostCount)), "Semaphore failure 1a: bad signal test.\n");
+			EATEST_VERIFY_MSG(bstd.mnErrorWaitCount == 0, "Semaphore failure 1b: bad signal test.\n");
+			EATEST_VERIFY_MSG(bstd.mnOKWaitCount == (bstd.mnPostThreadCount * bstd.mnPostCount), "Semaphore failure 1c: bad signal test.\n");
+		}
+
+		{  // Multithreaded test
+
+			// Problem: In the case of the inter-process test below, since we are using a named semaphore it's possible that
+			// if two instances of this unit test are run on the same machine simultaneously, they will collide because
+			// they are using the same semaphore. This applies only to true multi-process-capable machines.
+			uint64_t            cycle64 = EA::StdC::Stopwatch::GetCPUCycle();
+			uint16_t            cycle16 = (uint16_t)((uint16_t)(cycle64 >> 48) ^ (uint16_t)(cycle64 >> 32) ^ (uint16_t)(cycle64 >> 16) ^ (uint16_t)(cycle64 >> 0)); // Munge all the bits together because some platforms might have zeroed low bits; others high bits. This will work for all.
+			char                name[16]; sprintf(name, "SMT%u", (unsigned)cycle16);
+			SemaphoreParameters semaphoreParameters(0, false, name);
+			#if defined(EA_PLATFORM_DESKTOP)
+			const int           kLoopCount = 16;
+			#else
+			const int           kLoopCount = 4;
+			#endif
+
+			for(int j = 1; j <= kLoopCount; j++) // Intentionally start with 1, as we use j below.
+			{
+				if(semaphoreParameters.mbIntraProcess)
+					semaphoreParameters.mbIntraProcess = false;
+				else
+					semaphoreParameters.mbIntraProcess = true;
+
+				SWorkData workData(semaphoreParameters); 
+				workData.mnPostCount = (j % 4);
+
+				const int      kThreadCount(kMaxConcurrentThreadCount - 1);
+				Thread         thread[kThreadCount];
+				ThreadId       threadId[kThreadCount];
+				Thread::Status status;
+				int i;
+
+				for(i = 0; i < kThreadCount; i++)
+				{
+					threadId[i] = thread[i].Begin(SemaphoreTestThreadFunction, &workData);
+
+					EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Semaphore failure 3a: Couldn't create thread.\n");
+				}
+
+				EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*2000, gTestLengthSeconds*2000);
+
+				workData.mbShouldQuit = true;
+
+				#if defined(EA_PLATFORM_DESKTOP)
+					EA::UnitTest::ThreadSleepRandom(500, 500);
+				#else
+					EA::UnitTest::ThreadSleepRandom(1000, 1000);
+				#endif
+
+				// We seed the semaphore with more posts because timing issues could cause the 
+				// worker threads to get hung waiting for posts but it really isn't because
+				// of a problem with the semaphore. By the time all threads have quit, the 
+				// expected count should be equal to the increment.
+				const int kIncrement = 20;
+				workData.mnExpectedCount += kIncrement; // AtomicInt32 operation
+				workData.mSemaphore.Post(kIncrement);    
+
+				#if defined(EA_PLATFORM_DESKTOP)
+					EA::UnitTest::ThreadSleepRandom(1000, 1000);
+				#else
+					EA::UnitTest::ThreadSleepRandom(2000, 2000);
+				#endif
+
+				for(i = 0; i < kThreadCount; i++)
+				{
+					if(threadId[i] != kThreadIdInvalid)
+					{
+						status = thread[i].WaitForEnd(GetThreadTime() + 30000);
+						EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Semaphore failure 3b: Thread(s) didn't end.\n");
+					}
+				}
+
+				// Normally the Semaphore::GetCount function returns a value that is volatile, but we know that 
+				// there aren't any threads using mSemaphore any more, so GetCount returns a value we can rely on.
+				EATEST_VERIFY_MSG(workData.mnExpectedCount == workData.mSemaphore.GetCount(), "Semaphore failure 3c: Unexpected value.\n");
+				if(workData.mnExpectedCount != workData.mSemaphore.GetCount())
+				{
+					EA::UnitTest::ReportVerbosity(1, "    Thread count: %d, Intraprocess: %s, Post count: %d, Expected count: %d, Semaphore GetCount: %d\n", 
+												  kThreadCount, semaphoreParameters.mbIntraProcess ? "yes" : "no", workData.mnPostCount, (int)workData.mnExpectedCount.GetValue(), workData.mSemaphore.GetCount());
+				}
+
+				nErrorCount += (int)workData.mnErrorCount;
+			}
+
+		}
+
+	#endif // EA_THREADS_AVAILABLE
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+
+
+

+ 217 - 0
test/thread/source/TestThreadSmartPtr.cpp

@@ -0,0 +1,217 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_thread.h>
+#include <eathread/shared_ptr_mt.h>
+#include <eathread/shared_array_mt.h>
+#include <eathread/eathread_atomic.h>  // required to test the c11 atomics macros
+
+using namespace EA::Thread;
+
+
+struct TestClass
+{
+	int x;
+};
+
+
+static bool IsTrue(bool b)
+{
+	return b;
+}
+
+static bool IsFalse(bool b)
+{
+	return !b;
+}
+
+
+namespace not_eastl
+{
+	// Stub of std::shared_ptr implementation used to exercise the C11 atomic
+	// macro expansion.  This was causing issues because the function names of
+	// the free standing eastl::shared_ptr functions and the C11 atomics
+	// functions collided.  Since the C11 atomics were implemented as macros we
+	// can not utilize any scoping so we are forced to prevent the problem
+	// polluting the global namespace.
+	template <typename T> class shared_ptr { };
+	template <typename T> inline bool atomic_is_lock_free(const shared_ptr<T>*) { return false; }
+	template <typename T> inline shared_ptr<T> atomic_load(const shared_ptr<T>* pSharedPtr) { return *pSharedPtr; }
+	template <typename T> inline shared_ptr<T> atomic_load_explicit(const shared_ptr<T>* pSharedPtr, ...) { return *pSharedPtr; }
+	template <typename T> inline void atomic_store(shared_ptr<T>* pSharedPtrA, shared_ptr<T> sharedPtrB) {}
+	template <typename T> inline void atomic_store_explicit(shared_ptr<T>* pSharedPtrA, shared_ptr<T> sharedPtrB, ...) {}
+	template <typename T> shared_ptr<T> atomic_exchange(shared_ptr<T>* pSharedPtrA, shared_ptr<T> sharedPtrB) { return sharedPtrB; }
+	template <typename T> inline shared_ptr<T> atomic_exchange_explicit(shared_ptr<T>* pSharedPtrA, shared_ptr<T> sharedPtrB, ...){ return *pSharedPtrA; }
+	template <typename T> bool atomic_compare_exchange_strong(shared_ptr<T>* pSharedPtr, shared_ptr<T>* pSharedPtrCondition, shared_ptr<T> sharedPtrNew) { return false; }
+	template <typename T> inline bool atomic_compare_exchange_weak(shared_ptr<T>* pSharedPtr, shared_ptr<T>* pSharedPtrCondition, shared_ptr<T> sharedPtrNew) { return false; }
+	template <typename T> inline bool atomic_compare_exchange_strong_explicit(shared_ptr<T>* pSharedPtr, shared_ptr<T>* pSharedPtrCondition, shared_ptr<T> sharedPtrNew, ...) { return  false; }
+	template <typename T> inline bool atomic_compare_exchange_weak_explicit(shared_ptr<T>* pSharedPtr, shared_ptr<T>* pSharedPtrCondition, shared_ptr<T> sharedPtrNew, ...) { return false; }
+}
+
+
+int TestThreadSmartPtr()
+{
+	int nErrorCount(0);
+
+	// C11 atomics & eastl::shared_ptr name collision tests.
+	{
+		// If you are seeing compiler errors regarding C11 atomic macro
+		// expansions here look at header file eathread_atomic_android_c11.h.
+		// Ensure C11 atomic macros are being undefined properly.  We hit this
+		// problem on android-gcc config.
+		using namespace not_eastl;
+		shared_ptr<int> ptr1, ptr2, ptr3;
+
+		atomic_is_lock_free(&ptr1);
+		atomic_load(&ptr1);
+		atomic_load_explicit(&ptr1);
+		atomic_store(&ptr1, ptr2);
+		atomic_store_explicit(&ptr1, ptr2);
+		atomic_exchange(&ptr1, ptr2);
+		atomic_exchange_explicit(&ptr1, ptr2);
+		atomic_compare_exchange_strong(&ptr1, &ptr2, ptr3);
+		atomic_compare_exchange_weak(&ptr1, &ptr2, ptr3);
+		atomic_compare_exchange_strong_explicit(&ptr1, &ptr2, ptr3);
+		atomic_compare_exchange_strong_explicit(&ptr1, &ptr2, ptr3);
+	}
+
+	{  // Basic single-threaded test.
+		typedef shared_ptr_mt<TestClass> TestClassSharedPtr;
+
+		// typedef T element_type;
+		EATEST_VERIFY_MSG(sizeof(TestClassSharedPtr::element_type) > 0, "shared_ptr_mt failure.");
+
+		// typedef T value_type;
+		EATEST_VERIFY_MSG(sizeof(TestClassSharedPtr::value_type) > 0, "shared_ptr_mt failure");
+
+		// explicit shared_ptr_mt(T* pValue = 0)
+		TestClassSharedPtr pSharedPtr0;
+		TestClassSharedPtr pSharedPtr1(new TestClass);
+		TestClassSharedPtr pSharedPtr2(new TestClass);
+
+		// shared_ptr_mt(shared_ptr_mt const& sharedPtr)
+		TestClassSharedPtr pSharedPtr3(pSharedPtr1);
+
+		// void lock() const
+		// void unlock() const
+		pSharedPtr0.lock();
+		pSharedPtr0.unlock();
+
+		// operator bool_() const
+		EATEST_VERIFY_MSG(IsFalse(pSharedPtr0), "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG( IsTrue(pSharedPtr1), "shared_ptr_mt failure");
+
+		// bool operator!() const
+		EATEST_VERIFY_MSG( IsTrue(!pSharedPtr0), "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(IsFalse(!pSharedPtr1), "shared_ptr_mt failure");
+
+		// int use_count() const
+		EATEST_VERIFY_MSG(pSharedPtr0.use_count() == 1, "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr1.use_count() == 2, "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr2.use_count() == 1, "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr3.use_count() == 2, "shared_ptr_mt failure");
+
+		// bool unique() const
+		EATEST_VERIFY_MSG( pSharedPtr0.unique(), "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(!pSharedPtr1.unique(), "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG( pSharedPtr2.unique(), "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(!pSharedPtr3.unique(), "shared_ptr_mt failure");
+
+		// shared_ptr_mt& operator=(shared_ptr_mt const& sharedPtr)
+		pSharedPtr3 = pSharedPtr2;
+
+		EATEST_VERIFY_MSG(pSharedPtr1.use_count() == 1, "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr2.use_count() == 2, "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr3.use_count() == 2, "shared_ptr_mt failure");
+
+		// void reset(T* pValue = 0)
+		pSharedPtr2.reset();
+
+		EATEST_VERIFY_MSG(IsFalse(pSharedPtr2), "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr1.use_count() == 1, "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr2.use_count() == 1, "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr3.use_count() == 1, "shared_ptr_mt failure");
+
+		pSharedPtr3.reset(new TestClass);
+
+		EATEST_VERIFY_MSG(IsTrue(pSharedPtr3), "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr3.use_count() == 1, "shared_ptr_mt failure");
+
+		// T* get() const
+		TestClass* pA = pSharedPtr1.get();
+		TestClass* pB = pSharedPtr3.get();
+
+		// template<class T>
+		// inline T* get_pointer(const shared_ptr_mt<T>& sharedPtr)
+		EATEST_VERIFY_MSG(get_pointer(pSharedPtr1) == pA, "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(get_pointer(pSharedPtr3) == pB, "shared_ptr_mt failure");
+
+		// void swap(shared_ptr_mt<T>& sharedPtr)
+		pSharedPtr1.swap(pSharedPtr3);
+
+		EATEST_VERIFY_MSG(pSharedPtr1.get() == pB, "shared_ptr_mt failure");
+		EATEST_VERIFY_MSG(pSharedPtr3.get() == pA, "shared_ptr_mt failure");
+
+		// T& operator*() const
+		(*pSharedPtr1).x = 37;
+
+		EATEST_VERIFY_MSG(pSharedPtr1.get()->x == 37, "shared_ptr_mt failure");
+
+		// T* operator->() const
+		pSharedPtr1->x = 73;
+
+		EATEST_VERIFY_MSG(pSharedPtr1.get()->x == 73, "shared_ptr_mt failure");
+
+		// template<class T>
+		// inline void swap(shared_ptr_mt<T>& sharedPtr1, shared_ptr_mt<T>& sharedPtr2)
+		swap(pSharedPtr1, pSharedPtr2);
+
+		EATEST_VERIFY_MSG(pSharedPtr2.get()->x == 73, "shared_ptr_mt failure");
+
+		// template<class T, class U>
+		// inline bool operator==(const shared_ptr_mt<T>& sharedPtr1, const shared_ptr_mt<U>& sharedPtr2)
+		bool bEqual = pSharedPtr1 == pSharedPtr2;
+
+		EATEST_VERIFY_MSG(!bEqual, "shared_ptr_mt failure");
+
+		// template<class T, class U>
+		// inline bool operator!=(const shared_ptr_mt<T>& sharedPtr1, const shared_ptr_mt<U>& sharedPtr2)
+		bool bNotEqual = pSharedPtr1 != pSharedPtr2;
+
+		EATEST_VERIFY_MSG(bNotEqual, "shared_ptr_mt failure");
+
+		// template<class T, class U>
+		// inline bool operator<(const shared_ptr_mt<T>& sharedPtr1, const shared_ptr_mt<U>& sharedPtr2)
+		bool bLessA = pSharedPtr1 < pSharedPtr2;
+		bool bLessB = pSharedPtr2 < pSharedPtr1;
+
+		EATEST_VERIFY_MSG(bLessA || bLessB, "shared_ptr_mt failure");
+	}
+
+
+	{
+		typedef shared_array_mt<TestClass> TestClassSharedArray;
+
+		TestClassSharedArray pSharedArray(new TestClass[5]);
+		TestClassSharedArray pSharedArray2(pSharedArray);
+
+		// To do: Implement the tests above.
+	}
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+
+
+
+

+ 75 - 0
test/thread/source/TestThreadSpinLock.cpp

@@ -0,0 +1,75 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_spinlock.h>
+
+
+using namespace EA::Thread;
+
+
+int TestThreadSpinLock()
+{
+	int nErrorCount(0);
+
+	{ // SpinLock -- Basic single-threaded test.
+
+		SpinLock spinLock;
+
+		EATEST_VERIFY_MSG(!spinLock.IsLocked(), "SpinLock failure");
+
+		spinLock.Lock();
+		EATEST_VERIFY_MSG(spinLock.IsLocked(), "SpinLock failure");
+
+		EATEST_VERIFY_MSG(!spinLock.TryLock(), "SpinLock failure");
+
+		spinLock.Unlock();
+		EATEST_VERIFY_MSG(!spinLock.IsLocked(), "SpinLock failure");
+
+		EATEST_VERIFY_MSG(spinLock.TryLock(), "SpinLock failure");
+		EATEST_VERIFY_MSG(spinLock.IsLocked(), "SpinLock failure");
+
+		spinLock.Unlock();
+		EATEST_VERIFY_MSG(!spinLock.IsLocked(), "SpinLock failure");
+	}
+
+
+	{ // AutoSpinLock -- Basic single-threaded test.
+
+		SpinLock spinLock;
+
+		EATEST_VERIFY_MSG(!spinLock.IsLocked(), "AutoSpinLock failure");
+
+		{  //Special scope just for the AutoSpinLock
+			AutoSpinLock autoSpinLock(spinLock);
+
+			EATEST_VERIFY_MSG(spinLock.IsLocked(), "AutoSpinLock failure");
+		}
+
+		EATEST_VERIFY_MSG(!spinLock.IsLocked(), "AutoSpinLock failure");
+	}
+
+
+	#if EA_THREADS_AVAILABLE
+		{  // Multithreaded test
+	  
+			// Implement this when we get the thread class working.
+			// gTestLengthSeconds;
+		}
+	#endif
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+
+
+

+ 236 - 0
test/thread/source/TestThreadStorage.cpp

@@ -0,0 +1,236 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread.h>
+#include <eathread/eathread_storage.h>
+#include <eathread/eathread_thread.h>
+
+using namespace EA::Thread;
+
+const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+
+///////////////////////////////////////////////////////////////////////////////
+// EA_THREAD_LOCAL tests
+//
+#ifdef EA_THREAD_LOCAL
+
+	// Test declaration of basic int.
+	EA_THREAD_LOCAL int gTLI = 0;
+
+	// Test declaration of static int.
+	static EA_THREAD_LOCAL int sTLI = 0;
+
+	// Test declaration of struct.
+	struct ThreadLocalData
+	{
+		int x;
+	};
+
+	EA_THREAD_LOCAL ThreadLocalData gTDL;
+
+#endif
+
+
+
+
+struct TLSWorkData
+{
+	AtomicInt32         mShouldBegin;
+	AtomicInt32         mShouldEnd;
+	AtomicInt32         mnErrorCount;
+	ThreadLocalStorage* mpTLS;
+
+	TLSWorkData()
+		: mShouldBegin(0), mShouldEnd(0), mnErrorCount(0), mpTLS(NULL) {}
+
+	// define copy ctor and assignment operator
+	// so the compiler does define them intrisically
+	TLSWorkData(const TLSWorkData& rhs);               // copy constructor
+	TLSWorkData& operator=(const TLSWorkData& rhs);    // assignment operator
+};
+
+
+
+static intptr_t TLSTestThreadFunction(void* pvWorkData)
+{
+	TLSWorkData* pWorkData = (TLSWorkData*)pvWorkData;
+	void*        pValue;
+	bool         bResult;
+	size_t       i = 1;
+	int          nErrorCount = 0;
+
+	while(pWorkData->mShouldBegin.GetValue() == 0) // Spin until we get the should-begin signal. We could also use a Semaphore for this, but our use here is very simple and not performance-oriented.
+		EA::Thread::ThreadSleep(100);
+
+	pValue = pWorkData->mpTLS->GetValue();
+	EATEST_VERIFY_MSG(pValue == NULL, "ThreadLocalStorage failure.");
+
+	do{
+		bResult = pWorkData->mpTLS->SetValue((void*)i);
+		EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure.");
+
+		pValue = pWorkData->mpTLS->GetValue();
+		EATEST_VERIFY_MSG(pValue == (void*)i, "ThreadLocalStorage failure.");
+
+		i++;
+
+		ThreadCooperativeYield(); // Used by cooperative threading platforms.
+	}while(pWorkData->mShouldEnd.GetValue() == 0);
+
+	pValue = pWorkData->mpTLS->GetValue();
+	EATEST_VERIFY_MSG(pValue != NULL, "ThreadLocalStorage failure.");
+
+	pWorkData->mnErrorCount += nErrorCount;
+
+	return 0;
+}
+
+
+static int TestThreadStorageSingle()
+{
+	int nErrorCount(0);
+
+	{  // Re-use test
+		void* pValue;
+		ThreadLocalStorage tlsUnused;
+
+		{ // Give this its own scope.
+			ThreadLocalStorage tls0;
+
+			pValue = tls0.GetValue();
+			EATEST_VERIFY_MSG(pValue == NULL, "ThreadLocalStorage failure.");
+
+			tls0.SetValue((void*)1);
+		}
+
+		ThreadLocalStorage tls1;
+
+		pValue = tls1.GetValue();
+		EATEST_VERIFY_MSG(pValue == NULL, "ThreadLocalStorage failure.");
+	}
+
+
+	{  // Single-threaded test
+		#ifdef EA_THREAD_LOCAL
+			// EA_THREAD_LOCAL tests
+			gTLI   = 1;
+			sTLI   = 1;
+			gTDL.x = 1;
+
+			if((gTLI + sTLI + gTDL.x) == 100000) // Prevent compiler warnings due to non-usage.
+				EA::UnitTest::ReportVerbosity(1, "EA_THREAD_LOCAL: %d %d %d\n", gTLI, sTLI, gTDL.x);
+		#endif
+
+		// Test ThreadLocalStorage
+		ThreadLocalStorage tlsA;
+		ThreadLocalStorage tlsB;
+		ThreadLocalStorage tlsC;
+		void* pValue;
+		void* pNULL = (void*)0;
+		void* p1    = (void*)1;
+		void* p2    = (void*)2;
+		bool  bResult;
+
+		pValue = tlsA.GetValue();
+		EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure.");
+
+		pValue = tlsB.GetValue();
+		EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure.");
+
+		pValue = tlsC.GetValue();
+		EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure.");
+
+		bResult = tlsA.SetValue(pNULL);
+		EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure.");
+
+		pValue = tlsA.GetValue();
+		EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure.");
+
+		bResult = tlsA.SetValue(p1);
+		EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure.");
+
+		pValue = tlsA.GetValue();
+		EATEST_VERIFY_MSG(pValue == p1, "ThreadLocalStorage failure.");
+
+		bResult = tlsA.SetValue(pNULL);
+		EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure.");
+
+		pValue = tlsA.GetValue();
+		EATEST_VERIFY_MSG(pValue == pNULL, "ThreadLocalStorage failure.");
+
+		bResult = tlsA.SetValue(p2);
+		EATEST_VERIFY_MSG(bResult, "ThreadLocalStorage failure.");
+
+		pValue = tlsA.GetValue();
+		EATEST_VERIFY_MSG(pValue == p2, "ThreadLocalStorage failure.");
+	}
+
+	return nErrorCount;
+}
+
+
+static int TestThreadStorageMultiple()
+{
+	int nErrorCount(0);
+
+	const int      kThreadCount(kMaxConcurrentThreadCount - 1);
+	Thread         thread[kThreadCount];
+	ThreadId       threadId[kThreadCount];
+	Thread::Status status;
+	int            i;
+	TLSWorkData    workData;
+
+	for(i = 0; i < kThreadCount; i++)
+	{
+		threadId[i] = thread[i].Begin(TLSTestThreadFunction, &workData);
+		EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: Couldn't create thread.");
+	}
+
+	workData.mpTLS = new ThreadLocalStorage;
+	workData.mShouldBegin.SetValue(1);
+
+	EA::UnitTest::ThreadSleepRandom(2000, 2000);
+
+	workData.mShouldEnd.SetValue(1);
+
+	EA::UnitTest::ThreadSleepRandom(1000, 1000);
+
+	for(i = 0; i < kThreadCount; i++)
+	{
+		if(threadId[i] != kThreadIdInvalid)
+		{
+			status = thread[i].WaitForEnd(GetThreadTime() + 30000);
+			EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread(s) didn't end.");
+		}
+	}
+
+	delete workData.mpTLS;
+	workData.mpTLS = NULL;
+
+	nErrorCount += (int)workData.mnErrorCount;
+
+	return nErrorCount;
+}
+
+
+int TestThreadStorage()
+{
+	int nErrorCount(0);
+
+	nErrorCount += TestThreadStorageSingle();
+
+	#if EA_THREADS_AVAILABLE
+		// Call this twice, to make sure recyling of TLS works properly.
+		nErrorCount += TestThreadStorageMultiple();
+		nErrorCount += TestThreadStorageMultiple();
+	#endif
+
+	return nErrorCount;
+}
+
+
+

+ 26 - 0
test/thread/source/TestThreadSync.cpp

@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_sync.h>
+#include <eathread/eathread.h>
+
+
+int TestThreadSync()
+{
+	using namespace EA::Thread;
+
+	int nErrorCount(0);
+
+	EAReadBarrier();
+	EAWriteBarrier();
+	EAReadWriteBarrier();
+	EACompilerMemoryBarrier();
+
+	return nErrorCount;
+}
+
+
+

+ 1345 - 0
test/thread/source/TestThreadThread.cpp

@@ -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, &params);
+
+		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, &params);  // 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, &params);  
+
+		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, &params);  // 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(&regularThread, NULL, &regularThread.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, &params);
+
+			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;
+}
+
+
+
+
+
+
+
+
+

+ 100 - 0
test/thread/source/TestThreadThreadPool.cpp

@@ -0,0 +1,100 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright (c) Electronic Arts Inc. All rights reserved.
+///////////////////////////////////////////////////////////////////////////////
+
+#include "TestThread.h"
+#include <EATest/EATest.h>
+#include <eathread/eathread_pool.h>
+#include <eathread/eathread_atomic.h>
+#include <stdlib.h>
+
+
+using namespace EA::Thread;
+
+
+const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
+
+
+struct TPWorkData
+{
+   int mnWorkItem;
+   TPWorkData(int nWorkItem) : mnWorkItem(nWorkItem) {}
+};
+
+
+static AtomicInt32 gWorkItemsCreated   = 0;
+static AtomicInt32 gWorkItemsProcessed = 0;
+
+
+static intptr_t WorkerFunction(void* pvWorkData)
+{
+   TPWorkData* pWorkData = (TPWorkData*)pvWorkData;
+
+   ThreadId threadId = GetThreadId();
+   EA::UnitTest::ReportVerbosity(1, "Work %4d starting for thread %s.\n", pWorkData->mnWorkItem, EAThreadThreadIdToString(threadId));
+
+   EA::UnitTest::ThreadSleepRandom(200, 600);
+   ++gWorkItemsProcessed;
+
+   EA::UnitTest::ReportVerbosity(1, "Work %4d ending for thread   %s.\n", pWorkData->mnWorkItem, EAThreadThreadIdToString(threadId));
+
+   delete pWorkData;
+
+   return 0;
+}
+
+
+int TestThreadThreadPool()
+{
+	int nErrorCount(0);
+
+	#if EA_THREADS_AVAILABLE
+		{
+			ThreadPoolParameters tpp;
+			tpp.mnMinCount                = kMaxConcurrentThreadCount - 1;
+			tpp.mnMaxCount                = kMaxConcurrentThreadCount - 1;
+			tpp.mnInitialCount            = 0;
+
+			tpp.mnIdleTimeoutMilliseconds = EA::Thread::kTimeoutNone;  // left in to test the usage of this kTimeout* defines.
+			tpp.mnIdleTimeoutMilliseconds = 20000;
+			
+
+			ThreadPool threadPool(&tpp);
+			int        nResult; 
+
+			for(unsigned int i = 0; i < gTestLengthSeconds * 3; i++)
+			{
+				const int nWorkItem = (int)gWorkItemsCreated++;
+				TPWorkData* const pWorkData = new TPWorkData(nWorkItem);
+
+				EA::UnitTest::ReportVerbosity(1, "Work %4d created.\n", nWorkItem);
+				nResult = threadPool.Begin(WorkerFunction, pWorkData, NULL, true);
+
+				EATEST_VERIFY_MSG(nResult != ThreadPool::kResultError, "Thread pool failure in Begin.");
+
+				EA::UnitTest::ThreadSleepRandom(300, 700);
+				//Todo: If the pool task length gets too long, wait some more.
+			}
+
+			EA::UnitTest::ReportVerbosity(1, "Shutting down thread pool.\n");
+			bool bShutdownResult = threadPool.Shutdown(ThreadPool::kJobWaitAll, GetThreadTime() + 60000);
+
+			EATEST_VERIFY_MSG(bShutdownResult, "Thread pool failure in Shutdown (waiting for jobs to complete).");
+		}
+
+		EATEST_VERIFY_MSG(gWorkItemsCreated == gWorkItemsProcessed, "Thread pool failure: gWorkItemsCreated != gWorkItemsProcessed.");
+	#endif
+
+	return nErrorCount;
+}
+
+
+
+
+
+
+
+
+
+
+