///////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2005-2017 Dawson Dean // // 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. // ///////////////////////////////////////////////////////////////////////////// // // Threads Module // // This wraps the basic mechanisms of a threading library: threads, locks, // semaphores. They are all in one module because they are usually implemented // as a single package (like pthreads) and they also have inter-dependencies // (like sometimes a semaphore grabs the lock when it wakes a thread). // // The lock is a reference counted lock, which allows several different related // data structures to share a single lock. This reduces the number of locks // required by higher level modules, which saves resources (each lock can // create a kernel object) and also simplifies lock dependencies, which reduces // deadlock opportunities. This is a recursive lock, so a single thread may // reacquire the same lock multiple times. // // This is built on top of the lower level basic lock in OSIndependentLayer. // It adds a few additional features, such as it will not implement the final release // and delete the lock until the lock has been completely unlocked. // // This also defines the AutoLock object, which will aquire and release a lock // when a variable enters and leaves scope. It's like a monitor lock, except it // applies to any C scope, including the body of a procedure, a loop body, an // if statement, and more. // // This also implements CRefEvent, a cross-platform binary semaphore. ///////////////////////////////////////////////////////////////////////////// #if LINUX #include "time.h" #include "signal.h" #endif // LINUX #include "osIndependantLayer.h" #include "log.h" #include "config.h" #include "debugging.h" #include "refCount.h" #include "memAlloc.h" #include "threads.h" FILE_DEBUGGING_GLOBALS(LOG_LEVEL_DEFAULT, 0); static CRefLock *g_ThreadStateLock = NULL; static CSimpleThread *g_GlobalThreadList; #if WIN32 static void Win32ThreadStart(void *arg); #elif LINUX static void *LinuxThreadStart(void *arg); #endif #if LINUX extern void X86AtomicDecrement(int32 *p); extern void X86AtomicIncrement(int32 *p); //#define PTHREAD_COND_INITIALIZER 0 #endif // LINUX ///////////////////////////////////////////////////////////////////////////// // // [InitializeModule] // ///////////////////////////////////////////////////////////////////////////// ErrVal CSimpleThread::InitializeModule() { ErrVal err = ENoErr; CSimpleThread *pThread; g_ThreadStateLock = CRefLock::Alloc(); if (NULL == g_ThreadStateLock) { gotoErr(EFail); } // Allocate the state for the main thread in the process. // This does NOT spin up a new thread. It simply makes a thread state // for the thread that is currently executing. pThread = newex CSimpleThread; if (NULL == pThread) { gotoErr(EFail); } pThread->m_ThreadFlags |= CSimpleThread::ORIGINAL_PROCESS_THREAD; err = pThread->RunThread("main", NULL, NULL); if (err) { gotoErr(err); } #if LINUX // Ignore SIGPIPE signals. signal(SIGPIPE, SIG_IGN); #endif // LINUX abort: returnErr(err); } // InitializeModule. ///////////////////////////////////////////////////////////////////////////// // // [CreateThread] // ///////////////////////////////////////////////////////////////////////////// ErrVal CSimpleThread::CreateThread( const char *pThreadName, ThreadProcType clientProc, void *pThreadArg, CSimpleThread **ppResultThread) { ErrVal err = ENoErr; CSimpleThread *pThread = NULL; if (NULL != ppResultThread) { *ppResultThread = NULL; } pThread = newex CSimpleThread; if (NULL == pThread) { gotoErr(EFail); } err = pThread->RunThread(pThreadName, clientProc, pThreadArg); if (err) { gotoErr(err); } if (NULL != ppResultThread) { *ppResultThread = pThread; } abort: returnErr(err); } // CreateThread. ///////////////////////////////////////////////////////////////////////////// // // [CSimpleThread] // ///////////////////////////////////////////////////////////////////////////// CSimpleThread::CSimpleThread() { m_ThreadFlags = 0; m_pThreadProc = NULL; m_pThreadArg = NULL; #if WIN32 m_hThreadHandle = 0; m_OSThreadId = 0; #elif LINUX m_hThread = 0; #endif m_pNextRunningThread = NULL; } // CSimpleThread. ///////////////////////////////////////////////////////////////////////////// // // [~CSimpleThread] // ///////////////////////////////////////////////////////////////////////////// CSimpleThread::~CSimpleThread() { m_pThreadProc = NULL; m_pThreadArg = NULL; } // CSimpleThread. ///////////////////////////////////////////////////////////////////////////// // // [RunThread] // // This initializes a thread: it assigns a thread ID, adds // the thread to the global state of all threads, and then // runs the thread. // // NOTE: The thread may have been allocated either in this // module, or some higher level module that subclasses thread. ///////////////////////////////////////////////////////////////////////////// ErrVal CSimpleThread::RunThread( const char *pName, ThreadProcType clientProc, void *threadArgArg) { ErrVal err = ENoErr; RunChecks(); pName = pName; // Add thread to active list. g_ThreadStateLock->Lock(); m_pNextRunningThread = g_GlobalThreadList; g_GlobalThreadList = this; g_ThreadStateLock->Unlock(); // Initialize the state of this thread. m_pThreadProc = clientProc; m_pThreadArg = threadArgArg; // If this is the main thread for the process, then it is // already running. Otherwise, start the thread. if (m_ThreadFlags & ORIGINAL_PROCESS_THREAD) { #if WIN32 m_hThreadHandle = GetCurrentThread(); m_OSThreadId = 0; #endif } else { #if WIN32 m_hThreadHandle = ::CreateThread( NULL, // pointer to thread security attributes 0, // initial thread stack size, in bytes. 0 Uses default size (LPTHREAD_START_ROUTINE) Win32ThreadStart, // pointer to thread function (LPVOID) this, // argument for new thread 0, // creation flags, CREATE_SUSPENDED &m_OSThreadId); // pointer to returned thread identifier if (NULL == m_hThreadHandle) { DWORD dwErr = GetLastError(); DEBUG_LOG("CSimpleThread::RunThread. CreateThread failed (step 2). GetLastError = %d", dwErr); gotoErr(TranslateWin32ErrorIntoErrVal(dwErr, true)); } #elif LINUX int result; pthread_attr_t attr; pthread_attr_init(&attr); result = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (0 != result) { gotoErr(EFail); } result = pthread_create(&m_hThread, &attr, LinuxThreadStart, this); if (0 != result) { gotoErr(EFail); } #endif m_ThreadFlags |= THREAD_STARTED; } abort: returnErr(err); } // RunThread. ///////////////////////////////////////////////////////////////////////////// // // [CheckState] // // This checks the state of the object itself; it is part of // the debugObject class. ///////////////////////////////////////////////////////////////////////////// ErrVal CSimpleThread::CheckState() { ErrVal err = ENoErr; CSimpleThread *currentThread; // Get exclusive access to the global thread state. g_ThreadStateLock->Lock(); currentThread = g_GlobalThreadList; while (currentThread) { if ((NULL == currentThread->m_pThreadProc) && !(currentThread->m_ThreadFlags & ORIGINAL_PROCESS_THREAD)) { gotoErr(EFail); } currentThread = currentThread->m_pNextRunningThread; } abort: g_ThreadStateLock->Unlock(); returnErr(err); } // CheckState. ///////////////////////////////////////////////////////////////////////////// // // These are the OS-specific thread stubs. ///////////////////////////////////////////////////////////////////////////// #if WIN32 static void Win32ThreadStart(void *arg) { CSimpleThread *pThreadState = (CSimpleThread *) arg; DEBUG_LOG("CSimpleThread: Starting thread"); if (NULL != pThreadState) { // Run the thread. // This call won't return until the thread has exited. __try { pThreadState->OSCallback(); } __except (EXCEPTION_EXECUTE_HANDLER) { DEBUG_WARNING("Caught thread exception."); } } DEBUG_LOG("CSimpleThread: Thread exits"); // Don't delete the actual thread descriptor. We keep that around // to do things like examine the exit code or check if the thread is still // running. // Kill the thread. ExitThread(0); // We never reach here. } // Win32ThreadStart. #endif ///////////////////////////////////////////////////////////////////////////// // // These are the OS-specific thread stubs. ///////////////////////////////////////////////////////////////////////////// #if LINUX static void * LinuxThreadStart(void *arg) { CSimpleThread *pThreadState; // Ignore SIGPIPE signals. signal(SIGPIPE, SIG_IGN); DEBUG_LOG("CSimpleThread: Starting thread"); pThreadState = (CSimpleThread *) arg; if (NULL != pThreadState) { // Run the thread. // This call won't return until the thread has exited. try { pThreadState->OSCallback(); } catch (void *p) { DEBUG_WARNING("Caught thread exception."); } } DEBUG_LOG("CSimpleThread: Thread exits"); // Don't delete the actual thread descriptor. We keep that around // to do things like examine the exit code or check if the thread is still // running. // Kill the thread. pthread_exit(pThreadState); // We never reach here. return(NULL); } // LinuxThreadStart. #endif ///////////////////////////////////////////////////////////////////////////// // // [OSCallback] // // This runs as part of the new thread. ///////////////////////////////////////////////////////////////////////////// void CSimpleThread::OSCallback() { CSimpleThread *prevThread; CSimpleThread *currentThread; // This is the thread body. if (m_pThreadProc) { (*m_pThreadProc)(m_pThreadArg, this); } // When this routine returns, the thread is done. m_ThreadFlags |= THREAD_STOPPED; // Get exclusive access to the global thread state. g_ThreadStateLock->Lock(); // Remove the thread from the active list. prevThread = NULL; currentThread = g_GlobalThreadList; while (currentThread) { if (currentThread == this) { if (g_GlobalThreadList == currentThread) { g_GlobalThreadList = m_pNextRunningThread; } else { prevThread->m_pNextRunningThread = m_pNextRunningThread; } break; } prevThread = currentThread; currentThread = currentThread->m_pNextRunningThread; } g_ThreadStateLock->Unlock(); // Close up the thread kernel object. This tells ExitThread that // there are no more references to the thread object so it can be deleted. #if WIN32 if (NULL != m_hThreadHandle) { (void) CloseHandle(m_hThreadHandle); m_hThreadHandle = NULL; } #endif } // OSCallback. ///////////////////////////////////////////////////////////////////////////// // // [IsRunning] // ///////////////////////////////////////////////////////////////////////////// bool CSimpleThread::IsRunning() { if (!(m_ThreadFlags & THREAD_STARTED) || (m_ThreadFlags & THREAD_STOPPED)) { return(false); } #if WIN32 { BOOL fSuccess; DWORD dwExitCode; fSuccess = GetExitCodeThread(m_hThreadHandle, &dwExitCode); if (!fSuccess) { return(false); } if (STILL_ACTIVE == dwExitCode) { return(true); } } #endif return(true); } // IsRunning. ///////////////////////////////////////////////////////////////////////////// // // [WaitForThreadToStop] // ///////////////////////////////////////////////////////////////////////////// void CSimpleThread::WaitForThreadToStop() { if (!(m_ThreadFlags & THREAD_STARTED) || (m_ThreadFlags & THREAD_STOPPED)) { return; } DEBUG_LOG("CSimpleThread::WaitForThreadToStop: Starting to wait"); #if WIN32 WaitForSingleObject(m_hThreadHandle, INFINITE); #elif LINUX pthread_join(m_hThread, NULL); #endif DEBUG_LOG("CSimpleThread::WaitForThreadToStop: Finished waiting"); } // WaitForThreadToStop. ///////////////////////////////////////////////////////////////////////////// // // [Alloc] // ///////////////////////////////////////////////////////////////////////////// CRefLock * CRefLock::Alloc() { ErrVal err = ENoErr; CRefLock *pLock = NULL; pLock = newex CRefLock; if (NULL == pLock) { gotoErr(EFail); } err = pLock->Initialize(); if (err) { RELEASE_OBJECT(pLock); gotoErr(err); } abort: return(pLock); } // Alloc ///////////////////////////////////////////////////////////////////////////// // // [CRefLock] // ///////////////////////////////////////////////////////////////////////////// CRefLock::CRefLock() { m_LockFlags = 0; m_RecursionDepth = 0; } // CRefLock. ///////////////////////////////////////////////////////////////////////////// // // [~CRefLock] // ///////////////////////////////////////////////////////////////////////////// CRefLock::~CRefLock() { if (m_LockFlags & LOCK_INITIALIZED) { m_hLockMutex.Shutdown(); m_LockFlags &= ~LOCK_INITIALIZED; m_RecursionDepth = 0; } } // ~CRefLock. ///////////////////////////////////////////////////////////////////////////// // // [Initialize] // // This initializes a lock. In some OS'es, this can return an error. ///////////////////////////////////////////////////////////////////////////// ErrVal CRefLock::Initialize() { ErrVal err = ENoErr; if (m_LockFlags & LOCK_INITIALIZED) { DEBUG_WARNING("CRefLock::Initialize. Multiple initializations of a single lock."); } else { // if (!(m_LockFlags & LOCK_INITIALIZED)) err = m_hLockMutex.Initialize(); if (err) { DEBUG_WARNING("Cannot initialize an os lock."); gotoErr(err); } m_LockFlags = LOCK_INITIALIZED; m_RecursionDepth = 0; } abort: returnErr(err); } // Initialize. ///////////////////////////////////////////////////////////////////////////// // // [Lock] // // This acquires a lock, it blocks until it has the lock. ///////////////////////////////////////////////////////////////////////////// void CRefLock::Lock() { if (!(m_LockFlags & LOCK_INITIALIZED)) { DEBUG_WARNING(" Lock is not initialized"); return; } m_hLockMutex.BasicLock(); // Once we hold the lock, update the lock state. m_LockFlags |= LOCKED; m_RecursionDepth += 1; } // Lock. ///////////////////////////////////////////////////////////////////////////// // // [Unlock] // // This releases a lock. ///////////////////////////////////////////////////////////////////////////// void CRefLock::Unlock() { bool fDeleteThis = false; if (!(m_LockFlags & LOCK_INITIALIZED)) { DEBUG_WARNING("CRefLock::Unlock. Lock is not initialized"); return; } // Update the lock state while we still hold the lock. m_RecursionDepth = m_RecursionDepth - 1; if (m_RecursionDepth < 0) { DEBUG_WARNING("CRefLock::Unlock. More Unlocks than locks."); m_RecursionDepth = 0; } if (0 == m_RecursionDepth) { m_LockFlags &= ~LOCKED; if (m_LockFlags & FINAL_RELEASE_ON_UNLOCK) { fDeleteThis = true; } } m_hLockMutex.BasicUnlock(); // We may have waited until the lock is unlocked until we can delete it. if (fDeleteThis) { DefaultReleaseImpl("Delayed release of a lock", 0); } } // Unlock. ///////////////////////////////////////////////////////////////////////////// // // [IsLocked] // ///////////////////////////////////////////////////////////////////////////// bool CRefLock::IsLocked() { if (m_LockFlags & LOCKED) { return(true); } else { return(false); } } // IsLocked ///////////////////////////////////////////////////////////////////////////// // // [ReleaseImpl] // // This is part of the CRefCountInterface class. We implement the added feature // here that we do not do a final release until the lock is unlocked. ///////////////////////////////////////////////////////////////////////////// void CRefLock::ReleaseImpl(const char *pFileName, int32 lineNum) { int32 newRefCount; #if DD_DEBUG // Check if we are touching an object after it should have // been deleted. if (m_RefCountFlags & PENDING_DELETE) { DEBUG_WARNING("CRefLock::ReleaseImpl. Using a deleted refcounted object"); } if (m_RefCountFlags & TRACK_REFCOUNT) { SignObjectImpl( CDebugObject::RELEASE_PROCEDURE, NULL, pFileName, lineNum, m_cRef); } #endif #if WIN32 newRefCount = InterlockedDecrement((LONG *) &m_cRef); #elif LINUX X86AtomicDecrement(&m_cRef); newRefCount = m_cRef; #endif // Decrement the refcount. If this is the last reference to the lock, // then we *may* delete it, but only if it is unlocked. If it is locked, // then we wait until it is unlocked before deleting it. if (0 == newRefCount) { #if WIN32 InterlockedIncrement((LONG *) &m_cRef); #elif LINUX X86AtomicIncrement(&m_cRef); #endif if (!(m_LockFlags & LOCKED)) { DefaultReleaseImpl("Fake second release of a lock", -1); } else { m_LockFlags |= FINAL_RELEASE_ON_UNLOCK; } } // if (0 == newRefCount) } // ReleaseImpl ///////////////////////////////////////////////////////////////////////////// // // [CRefEvent] // ///////////////////////////////////////////////////////////////////////////// CRefEvent::CRefEvent() { #if WIN32 m_hEvent = NULL; #elif LINUX //m_hEvent = PTHREAD_COND_INITIALIZER; //m_EventLock = xxxx; m_NumWaiters = 0; m_NumSignals = 0; #endif m_fInitialized = false; } // CRefEvent. ///////////////////////////////////////////////////////////////////////////// // // [~CRefEvent] // ///////////////////////////////////////////////////////////////////////////// CRefEvent::~CRefEvent() { bool fSuccess = true; if (m_fInitialized) { #if WIN32 BOOL fWinSuccess = CloseHandle(m_hEvent); if (!fWinSuccess) { fSuccess = false; } m_hEvent = NULL; #elif LINUX int result; result = pthread_cond_destroy(&m_hEvent); if (0 != result) { fSuccess = false; } pthread_mutex_destroy(&m_EventLock); #endif } if (!fSuccess) { DEBUG_WARNING("Error while shutting down a CRefEvent."); } } // ~CRefEvent. ///////////////////////////////////////////////////////////////////////////// // // [Initialize] // ///////////////////////////////////////////////////////////////////////////// ErrVal CRefEvent::Initialize() { if (m_fInitialized) { DEBUG_WARNING("CRefEvent::Initialize. Initializing a CRefEvent twice."); return(ENoErr); } #if WIN32 m_hEvent = CreateEvent( NULL, // pointer to security attributes false, // flag for manual-reset event false, // flag for initial state NULL); // pointer to event-object name if (NULL != m_hEvent) { m_fInitialized = true; } #elif LINUX // pthread_condattr_t attr; pthread_mutexattr_t mutexAttr; int result; result = pthread_cond_init(&m_hEvent, NULL); if (0 != result) { goto abort; } // From the pthreads doc: // // The pthread_cond_wait() and pthread_cond_timedwait() functions are used // to block on a condition variable. They are called with mutex locked by // the calling thread or undefined behaviour will result. // // So, I have to pass in a mutex or else pthread_cond_wait crashes. // // Moreover, condition variables do not store their value, so doing // a signal before a wait will leave the wait blocked. So, I use the lock // and maintain my own count of signals. pthread_mutexattr_init(&mutexAttr); result = pthread_mutexattr_settype(&mutexAttr, PTHREAD_MUTEX_RECURSIVE_NP); if (0 != result) { goto abort; } result = pthread_mutex_init(&m_EventLock, &mutexAttr); if (0 != result) { goto abort; } m_fInitialized = true; abort: #endif if (!m_fInitialized) { DEBUG_WARNING("CRefEvent::Initialize. Cannot initialize a CRefEvent"); returnErr(EFail); } returnErr(ENoErr); } // Initialize. ///////////////////////////////////////////////////////////////////////////// // // [Wait] // ///////////////////////////////////////////////////////////////////////////// void CRefEvent::Wait() { if (!m_fInitialized) { DEBUG_WARNING("CRefEvent::Wait. Uninitialized semaphore."); return; } #if WIN32 DWORD dwResult = WaitForSingleObject(m_hEvent, INFINITE); if (WAIT_FAILED == dwResult) { DEBUG_WARNING("CRefEvent::Wait. Bad lock error."); } #elif LINUX int result = 0; pthread_mutex_lock(&m_EventLock); // Loop until we have success. Several waiting threads may be // competing for the same signal. while (1) { if (m_NumSignals > 0) { m_NumSignals--; break; } else { m_NumWaiters += 1; result = pthread_cond_wait(&m_hEvent, &m_EventLock); m_NumWaiters--; } } // while (1) pthread_mutex_unlock(&m_EventLock); if (0 != result) { DEBUG_WARNING("CRefEvent::Wait. Bad lock error."); } #endif } // Wait ///////////////////////////////////////////////////////////////////////////// // // [Signal] // ///////////////////////////////////////////////////////////////////////////// void CRefEvent::Signal() { if (!m_fInitialized) { DEBUG_WARNING("CRefEvent::Signal. Uninitialized semaphore."); return; } #if WIN32 BOOL fSuccess; fSuccess = SetEvent(m_hEvent); if (!fSuccess) { DEBUG_WARNING("CRefEvent::Signal. Error in CRefEvent::Signal."); } #elif LINUX int result = 0; pthread_mutex_lock(&m_EventLock); m_NumSignals++; // If some thread is sleeping for this signal, then wake it up. if (m_NumWaiters > 0) { result = pthread_cond_signal(&m_hEvent); } pthread_mutex_unlock(&m_EventLock); if (0 != result) { DEBUG_WARNING("CRefEvent::Signal. Error"); } #endif } // Signal ///////////////////////////////////////////////////////////////////////////// // // [Clear] // ///////////////////////////////////////////////////////////////////////////// void CRefEvent::Clear() { if (!m_fInitialized) { DEBUG_WARNING("Uninitialized semaphore."); return; } #if WIN32 BOOL fSuccess = ResetEvent(m_hEvent); if (!fSuccess) { DEBUG_WARNING("CRefEvent::Clear. Error"); } #elif LINUX pthread_mutex_lock(&m_EventLock); m_NumSignals = 0; // If threads are waiting on the signal, then don't wake them. We // are clearing the signal, so they will have to wait until it is set // again. pthread_mutex_unlock(&m_EventLock); #endif } // Clear ///////////////////////////////////////////////////////////////////////////// // // TESTING PROCEDURES // ///////////////////////////////////////////////////////////////////////////// #if INCLUDE_REGRESSION_TESTS #define NUM_TEST_LOCKS 100 #define NUM_RETRIES 2 #define NUM_MULTIPLE_LOCKS 3 #define NUM_TRIALS 2 #define REPEAT_ALL_TESTS 2 static void RunThreadTests(); static void RunLockTests(); static void TestThreadProc(void *arg, CSimpleThread *threadState); CRefLock * locks[NUM_TEST_LOCKS]; #define NUM_TEST_THREADS 30 static CRefEvent * g_WorkerThreadReady[NUM_TEST_THREADS]; static CRefEvent * g_WorkerAllowedToFinishSems[NUM_TEST_THREADS]; static int32 g_WorkerIDs[NUM_TEST_THREADS]; static int32 g_TestValues[NUM_TEST_THREADS]; ///////////////////////////////////////////////////////////////////////////// // // [TestThreads] // ///////////////////////////////////////////////////////////////////////////// void CSimpleThread::TestThreads() { int32 allTestNum; int32 lockNum; g_DebugManager.StartModuleTest("Threads"); for (lockNum = 0; lockNum < NUM_TEST_LOCKS; lockNum++) { locks[lockNum] = CRefLock::Alloc(); if (NULL == locks[lockNum]) { DEBUG_WARNING("Error from Initialize"); } } for (allTestNum = 0; allTestNum < REPEAT_ALL_TESTS; allTestNum++) { RunLockTests(); } RunThreadTests(); } // TestThreads. ///////////////////////////////////////////////////////////////////////////// // // [RunLockTests] // ///////////////////////////////////////////////////////////////////////////// static void RunLockTests() { int32 lockNum; int32 trial; g_DebugManager.SetProgressIncrement(1); ////////////////////////////////////////// g_DebugManager.StartTest("Get exclusive locks several times"); for (trial = 0; trial < NUM_TRIALS; trial++) { g_DebugManager.ShowProgress(); for (lockNum = 0; lockNum < NUM_TEST_LOCKS; lockNum++) { if (locks[lockNum]) { (locks[lockNum])->Lock(); } } for (lockNum = 0; lockNum < NUM_TEST_LOCKS; lockNum++) { g_DebugManager.ShowProgress(); if (locks[lockNum]) { (locks[lockNum])->Unlock(); } } } // testing exclusive locks. } // RunLockTests. ///////////////////////////////////////////////////////////////////////////// // // [TestThreadProc] // ///////////////////////////////////////////////////////////////////////////// static void TestThreadProc(void *arg, CSimpleThread *threadState) { int32 threadNum; threadState = threadState; threadNum = *((int32 *) arg); g_TestValues[threadNum] = 5; if (g_WorkerThreadReady[threadNum]) { g_WorkerThreadReady[threadNum]->Signal(); } if (g_WorkerAllowedToFinishSems[threadNum]) { g_WorkerAllowedToFinishSems[threadNum]->Wait(); } if (99 != g_TestValues[threadNum]) { DEBUG_WARNING("g_TestValues was not set correctly."); } g_TestValues[threadNum] = 13; if (g_WorkerThreadReady[threadNum]) { g_WorkerThreadReady[threadNum]->Signal(); } } // TestThreadProc. ///////////////////////////////////////////////////////////////////////////// // // [RunThreadTests] // ///////////////////////////////////////////////////////////////////////////// void RunThreadTests() { ErrVal err = ENoErr; int16 threadNum; for (threadNum = 0; threadNum < NUM_TEST_THREADS; threadNum++) { g_WorkerThreadReady[threadNum] = newex CRefEvent; if (NULL == g_WorkerThreadReady[threadNum]) { DEBUG_WARNING("Error from newex"); return; } g_WorkerAllowedToFinishSems[threadNum] = newex CRefEvent; if (NULL == g_WorkerAllowedToFinishSems[threadNum]) { DEBUG_WARNING("Error from newex"); return; } err = g_WorkerThreadReady[threadNum]->Initialize(); if (err) { DEBUG_WARNING("Error from event->Initialize"); return; } err = g_WorkerAllowedToFinishSems[threadNum]->Initialize(); if (err) { DEBUG_WARNING("Error from event->Initialize"); return; } g_TestValues[threadNum] = 0; } g_DebugManager.StartTest("Fork some threads"); for (threadNum = 0; threadNum < NUM_TEST_THREADS; threadNum++) { g_WorkerIDs[threadNum] = threadNum; err = CSimpleThread::CreateThread("thread", &TestThreadProc, (void *) &(g_WorkerIDs[threadNum]), NULL); if (err) { DEBUG_WARNING("Error from CSimpleThread::CreateThread"); } } g_DebugManager.StartTest("Wait for threads to do some work"); for (threadNum = 0; threadNum < NUM_TEST_THREADS; threadNum++) { g_WorkerThreadReady[threadNum]->Wait(); if (5 != g_TestValues[threadNum]) { DEBUG_WARNING("A thread did not set the right result value."); } g_TestValues[threadNum] = 99; g_WorkerThreadReady[threadNum]->Clear(); g_WorkerAllowedToFinishSems[threadNum]->Signal(); g_WorkerThreadReady[threadNum]->Wait(); if (13 != g_TestValues[threadNum]) { DEBUG_WARNING("A thread did not set the right result value."); } } } // RunThreadTests. #endif // INCLUDE_REGRESSION_TESTS