//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "platform/threads/thread.h" #include "platform/threads/semaphore.h" #include "platform/threads/mutex.h" #include "unit/test.h" #include "core/util/tVector.h" #include "console/console.h" using namespace UnitTesting; class ThreadTestHarness { U32 mStartTime, mEndTime, mCleanupTime; void (*mThreadBody)(void*); S32 mThreadCount; Thread **mThreads; public: ThreadTestHarness() { mStartTime = mEndTime = mCleanupTime = 0; mThreadBody = NULL; mThreadCount = 1; mThreads = NULL; } void startThreads(void (*threadBody)(void*), void *arg, U32 threadCount) { mThreadCount = threadCount; mThreadBody = threadBody; // Start up threadCount threads... mThreads = new Thread*[threadCount]; mStartTime = Platform::getRealMilliseconds(); //Con::printf(" Running with %d threads...", threadCount); for(S32 i=0; istart(); } } void waitForThreadExit(U32 checkFrequencyMs) { // And wait for them to complete. bool someAlive = true; S32 liveCount = mThreadCount; while(someAlive) { //Con::printf(" - Sleeping for %dms with %d live threads.", checkFrequencyMs, liveCount); Platform::sleep(checkFrequencyMs); someAlive = false; liveCount = 0; for(S32 i=0; iisAlive()) continue; someAlive = true; liveCount++; } } mEndTime = Platform::getRealMilliseconds(); // Clean up memory at this point. for(S32 i=0; iacquire(false), "Should succeed at acquiring a new semaphore with count 1."); test(sem2->acquire(false), "This one should succeed too, see previous test."); // Test that we can do non-blocking acquires that fail. test(sem1->acquire(false)==false, "Should failed, as we've already got the sem."); sem1->release(); test(sem2->acquire(false)==false, "Should also fail."); sem2->release(); // Test that we can do blocking acquires that succeed. test(sem1->acquire(true)==true, "Should succeed as we just released."); test(sem2->acquire(true)==true, "Should succeed as we just released."); // Can't test blocking acquires that never happen... :) // Clean up. delete sem1; delete sem2; } }; CreateUnitTest( SemaphoreWaitTest, "Platform/Threads/SemaphoreWaitTest") { static void threadBody(void *self) { SemaphoreWaitTest *me = (SemaphoreWaitTest*)self; // Wait for the semaphore to get released. me->mSemaphore->acquire(); // Increment the counter. Mutex::lockMutex(me->mMutex); me->mDoneCount++; Mutex::unlockMutex(me->mMutex); // Signal back to the main thread we're done. me->mPostbackSemaphore->release(); } Semaphore *mSemaphore; Semaphore *mPostbackSemaphore; void *mMutex; U32 mDoneCount; const static S32 csmThreadCount = 10; void run() { ThreadTestHarness tth; mDoneCount = 0; mSemaphore = new Semaphore(0); mPostbackSemaphore = new Semaphore(0); mMutex = Mutex::createMutex(); tth.startThreads(&threadBody, this, csmThreadCount); Platform::sleep(500); Mutex::lockMutex(mMutex); test(mDoneCount == 0, "no threads should have touched the counter yet."); Mutex::unlockMutex(mMutex); // Let 500 come out. for(S32 i=0; irelease(); // And wait for 500 postbacks. for(S32 i=0; iacquire(); Mutex::lockMutex(mMutex); test(mDoneCount == csmThreadCount / 2, "Didn't get expected number of done threads! (a)"); Mutex::unlockMutex(mMutex); // Ok, now do the rest. // Let 500 come out. for(S32 i=0; irelease(); // And wait for 500 postbacks. for(S32 i=0; iacquire(); Mutex::lockMutex(mMutex); test(mDoneCount == csmThreadCount, "Didn't get expected number of done threads! (b)"); Mutex::unlockMutex(mMutex); // Wait for the threads to exit - shouldn't have to wait ever though. tth.waitForThreadExit(10); // Make sure no one touched our data after shutdown time. Mutex::lockMutex(mMutex); test(mDoneCount == csmThreadCount, "Didn't get expected number of done threads! (c)"); Mutex::unlockMutex(mMutex); } }; CreateUnitTest( MutexWaitTest, "Platform/Threads/MutexWaitTest") { static void threadBody(void *self) { MutexWaitTest *me = (MutexWaitTest*)self; // Increment the counter. We'll block until the mutex // is open. Mutex::lockMutex(me->mMutex); me->mDoneCount++; Mutex::unlockMutex(me->mMutex); } void *mMutex; U32 mDoneCount; const static S32 csmThreadCount = 10; void run() { mMutex = Mutex::createMutex(); mDoneCount = 0; // We lock the mutex before we create any threads, so that all the threads // block on the mutex. Then we unlock it and let them all work their way // through the increment. Mutex::lockMutex(mMutex); ThreadTestHarness tth; tth.startThreads(&threadBody, this, csmThreadCount); Platform::sleep(5000); // Check count is still zero. test(mDoneCount == 0, "Uh oh - a thread somehow didn't get blocked by the locked mutex!"); // Open the flood gates... Mutex::unlockMutex(mMutex); // Wait for the threads to all finish executing. tth.waitForThreadExit(10); Mutex::lockMutex(mMutex); test(mDoneCount == csmThreadCount, "Hmm - all threads reported done, but we didn't get the expected count."); Mutex::unlockMutex(mMutex); // Kill the mutex. Mutex::destroyMutex(mMutex); } };