//+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1993. // // File: threads.cxx // // Contents: Classes that implement thread pool for link tracking. // // Classes: CGlobalThreadSet -- all threads in all pools // CThreadPool -- spare threads in a pool // CTask -- object with data associated with thread // // // Functions: StartTaskThread -- function called by CreateThread when // creating the thread for CTask // // // History: 1-Mar-95 BillMo Created. // // Notes: // // Codework: // //-------------------------------------------------------------------------- #define NO_INCLUDE_UNION 1 #include "shellprv.h" #pragma hdrstop #include "bldtrack.h" #ifdef ENABLE_TRACK extern "C" HRESULT TimeoutExpired( DWORD ); #include "threads.hxx" //-------------------------------------------------------------------------- // Debugging stuff. //-------------------------------------------------------------------------- #ifdef DBGTHREADS extern CRITICAL_SECTION g_cs; extern CGlobalThreadSet g_ts; int __cdecl myprintf(const char *pszArg, ...) { DWORD dw; EnterCriticalSection(&g_cs); va_list va; char buf[256]; va_start (va, pszArg); int r =vsprintf(buf, pszArg, va); va_end(va); LeaveCriticalSection(&g_cs); WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &dw, NULL); return r; } VOID _ThdAssert(char *psz, char *pszFile, int line) { myprintf("%08X, Assertion %s failed in %s at line %d\n", GetCurrentThreadId(), psz, pszFile, line); g_ts.SuspendAll(); Sleep(INFINITE); } DWORD _ThdWaitForSingleObject(char *psz, HANDLE h, DWORD dwTimeout) { ThdAssert(dwTimeout == INFINITE || dwTimeout == 0); while (TRUE) { DWORD dw = WaitForSingleObject(h, 5000); if (dw == WAIT_TIMEOUT && dwTimeout != 0) { myprintf("%08X, WaitForSingleObject timed out @ %s, " "threads=%d, pools=%d\n", GetCurrentThreadId(), psz, &g_cThreads, &g_cPools); continue; } else { return dw; } } } DWORD _ThdWaitForMultipleObjects(char *psz, DWORD c, CONST HANDLE *lph, BOOL fWaitAll, DWORD dwTimeout) { ThdAssert(dwTimeout == INFINITE); while (TRUE) { DWORD dw = WaitForMultipleObjects(c, lph, fWaitAll, 5000); if (dw == WAIT_TIMEOUT) { myprintf("%08X, WaitForMultipleObjects timed out @ %s, " "threads=%d, pools=%d\n", GetCurrentThreadId(), psz, &g_cThreads, &g_cPools); continue; } else { return dw; } } } VOID CGlobalThreadSet::SuspendAll() { ResumeThread(GetCurrentThread()); CTask *pCur = _pHeadTask; while (pCur) { SuspendThread(pCur->GetHandle()); pCur = pCur->GetNext(); } } #endif //+------------------------------------------------------------------- // // Member: CGlobalThreadSet::CGlobalThreadSet // // Synopsis: Initialize the object that keeps a reference to every // CTask object. // // Arguments: [phr] -- set to E_OUTOFMEMORY if failed to init. // // Notes: All waits on the GTS_LIST mutex also include the // CoUninitialize abort event (used by KillAllTasks) // so that we can force all threads to stop creating // more threads so we can wait for all the extant threads // to exit and then unload the dll. // //-------------------------------------------------------------------- CGlobalThreadSet::CGlobalThreadSet(HRESULT *phr) { _pHeadTask = NULL; _ahMultiWait[GTS_LIST] = CreateMutex(NULL, FALSE, NULL); _ahMultiWait[GTS_COUNINITIALIZE_ABORT_EVENT] = CreateEvent(NULL, TRUE, FALSE, NULL); if (_ahMultiWait[GTS_COUNINITIALIZE_ABORT_EVENT] == NULL || _ahMultiWait[GTS_LIST] == NULL) { *phr = E_OUTOFMEMORY; } } //+------------------------------------------------------------------- // // Member: CGlobalThreadSet::~CGlobalThreadSet // // Synopsis: Free resources used by this object. // // Notes: Object may not have constructed properly so we cleanup // carefully. // //-------------------------------------------------------------------- CGlobalThreadSet::~CGlobalThreadSet() { if (_ahMultiWait[GTS_LIST] != NULL) ThdVerify(CloseHandle(_ahMultiWait[GTS_LIST])); if (_ahMultiWait[GTS_COUNINITIALIZE_ABORT_EVENT] != NULL) ThdVerify(CloseHandle(_ahMultiWait[GTS_COUNINITIALIZE_ABORT_EVENT])); } //+------------------------------------------------------------------- // // Member: CGlobalThreadSet::CreateAndRecordThread // // Synopsis: Create a thread and insert into the global thread list. // // Arguments: [ppTask] -- where to put the pointer to the new CTask // [pPool] -- the pool that will be ref counted from // the new task. // // // Returns: MK_E_EXCEEDEDDEADLINE if the abort event is set. // E_OUTOFMEMORY // // Notes: Performs the insertion into the global list under the // protection of the _ahMultiWait[GTS_LIST] mutex // //-------------------------------------------------------------------- HRESULT CGlobalThreadSet::CreateAndRecordThread(CTask **ppTask, CThreadPool *pPool) { HRESULT hr = E_OUTOFMEMORY; DWORD index = ThdWaitForMultipleObjects("CreateAndRecordThread:GTS_LIST", 2, _ahMultiWait, FALSE, INFINITE); // GTS_LIST, GTS_COUNINITIALIZE_ABORT_EVENT if (index != WAIT_OBJECT_0 + GTS_LIST) { ThdAssert(index == WAIT_OBJECT_0 + GTS_COUNINITIALIZE_ABORT_EVENT); //assert(not the thread calling bind to object) ThdPrint("%08X, CreateAndRecordThread returning MK_E_EXCEEDEDDEADLINE\n", GetCurrentThreadId()); return(MK_E_EXCEEDEDDEADLINE); } *ppTask = new CTask(&hr); if (hr == S_OK) { (*ppTask)->SetNext(_pHeadTask); (*ppTask)->SetPool(pPool); _pHeadTask = *ppTask; } else { delete *ppTask; *ppTask = NULL; } ThdPrint("%08X, CreateAndRecordThread created pTask=%08X\n", GetCurrentThreadId(), *ppTask); ReleaseMutex(_ahMultiWait[GTS_LIST]); return(hr); } //+------------------------------------------------------------------- // // Member: CGlobalThreadSet::KillAllTasks // // Synopsis: Ensure all threads are killed so we can safely unload the // DLL that the thread code is executing in. // // Acquire the global thread list and walk it, aborting // each task in the list and waiting until the thread // handle is signalled. // // Notes: We acquire the global thread list mutex and once acquired // we set the abort event which will prevent any further // thread creations because the event is waited on whenever // the global thread list mutex is waited on. This means // that we can reliably kill all threads and KNOW they are // really killed because their handles are signalled. // //-------------------------------------------------------------------- VOID CGlobalThreadSet::KillAllTasks() { DWORD ret = ThdWaitForSingleObject("KillTasks:GTS_LIST", _ahMultiWait[GTS_LIST], INFINITE); ThdAssert (ret == WAIT_OBJECT_0 + GTS_LIST); ThdPrint("%08X, KillAllTasks setting COUNINITIALIZE_ABORT\n", GetCurrentThreadId()); SetEvent(_ahMultiWait[GTS_COUNINITIALIZE_ABORT_EVENT]); // force threads awake CTask *pCur = _pHeadTask; while (pCur) { CTask *pNext = pCur->GetNext(); pCur->AbortThread(); // waits until the thread is signalled delete pCur; pCur = pNext; } ReleaseMutex(_ahMultiWait[GTS_LIST]); } //+------------------------------------------------------------------- // // Member: CGlobalThreadSet::KillTasks // // Synopsis: Kill a selected set of tasks and remove them from // the global thread set. Wait on the thread handles // to wait for their complete cessation of activity. // // Arguments: [ppTasks] -- an array of CTask pointers // [cTasks] -- the count of CTask pointers // [pThisTask] -- this thread's CTask object ... make // sure that we don't wait for THIS threads // handle otherwise we'll wait forever. // // // Notes: Used by the pool to remove all the threads when it // has been detected that they are all spare. // //-------------------------------------------------------------------- VOID CGlobalThreadSet::KillTasks(CTask **ppTasks, int cTasks, CTask *pThisTask) { DWORD ret = ThdWaitForMultipleObjects("KillTasks:GTS_LIST", 2, _ahMultiWait, FALSE, INFINITE); // fWaitAll=FALSE if (ret == WAIT_OBJECT_0 + GTS_LIST) { int i; // signal each per-thread wait for work mutex and // set pThread->fAbort = TRUE ThdPrint("%08X, KillTasks aborting %d threads\n", GetCurrentThreadId(), cTasks); // spare thread list entries should be removed from the global list for (i=0; iAbortThread(); RemoveTask(ppTasks[i]); delete ppTasks[i]; } ReleaseMutex(_ahMultiWait[GTS_LIST]); } else { ThdAssert(ret == WAIT_OBJECT_0 + GTS_COUNINITIALIZE_ABORT_EVENT); } ThdPrint("%08X, KillTask: Thread Exit\n", GetCurrentThreadId()); ThdInterlockedDecrement(&g_cThreads); ExitThread(0); } //+------------------------------------------------------------------- // // Member: CGlobalThreadSet::RemoveTask // // Synopsis: Remove the specified task from the linked list of the // global thread set. // // Arguments: [pTask] -- the pointer to search for in the linked list. // // Algorithm: start pointing at the head // while pointing at a valid task // if its the one we want // if its not at the head // we must set previous to point to next // else // we must set head to point to next // //-------------------------------------------------------------------- VOID CGlobalThreadSet::RemoveTask(CTask *pTask) { CTask *pCur = _pHeadTask; CTask *pPrev = NULL; while (pCur != NULL) { if (pCur == pTask) { if (pPrev != NULL) { pPrev->SetNext(pCur->GetNext()); } else { // first time through _pHeadTask = pCur->GetNext(); } break; } pPrev = pCur; pCur = pCur->GetNext(); } } //+------------------------------------------------------------------- // // Member: CThreadPool::CThreadPool // // Synopsis: Create the bare thread pool. // // Arguments: [cMaxThreads] -- the maximum number of threads allowed // to be created // // [refAllThreads] -- reference to the CGlobalThreadSet // that should keep track of all the // created CTasks (threads) // // [dwTickCountDeadline] -- time at which the pool should // force completion // // [phr] -- set to S_OK if successful, otherwise not // initialized. // // Notes: _hEventComplete is initialized not signalled and // is signalled when all threads in the pool are spare and // will be killed, OR if one of the threads is successful // in performing the command (i.e. it finds the object.) // // _hEventEnableSecondTierThreads is enabled by the // user of the thread pool when he/she has created all // tier 1 threads. (this feature could be redesigned to // save the event.) // // _hSemNSpare is intended to be signalled if the thread // pool contains at least one spare thread or if there // is room for more threads in the pool // // _csSpare is a critical section which protects the counters // state of the pool // // _cPoolThreads is a count of the number of threads so far // created in the pool. // // _cRefs keeps the pool alive if there are any threads // referring to it. // //-------------------------------------------------------------------- CThreadPool::CThreadPool( int cMaxThreads, CGlobalThreadSet &refAllThreads, DWORD dwTickCountDeadline, HRESULT *phr) : _refAllThreads(refAllThreads), _dwTickCountDeadline(dwTickCountDeadline) { _hEventComplete = CreateEvent(NULL, TRUE, FALSE, NULL); _fEventComplete = FALSE; _hEventEnableSecondTierThreads = CreateEvent(NULL, TRUE, FALSE, NULL); _hSemNSpare = CreateSemaphore(NULL, 1, cMaxThreads, NULL); InitializeCriticalSection( &_csSpare ); _pUiCommand = NULL; // an improvement would be to use linked-lists! _ppSpareTasks = (CTask**) LocalAlloc(LMEM_FIXED, SIZEOF(CTask *) * cMaxThreads); _fTerminatePool = FALSE; _fHadSuccess = FALSE; _cPoolThreads = 0; _cMaxThreads = cMaxThreads; _cRefs = 1; _cSpare = 0; _fAllDying = FALSE; ThdInterlockedIncrement(&g_cPools); if (_hEventComplete && _hEventEnableSecondTierThreads && _hSemNSpare && _ppSpareTasks) *phr = S_OK; } //+------------------------------------------------------------------- // // Member: CThreadPool::~CThreadPool // // Synopsis: Release resources associated with pool. // //-------------------------------------------------------------------- CThreadPool::~CThreadPool() { //ThdAssert(WAIT_OBJECT_0 == WaitForSingleObject(_hEventComplete, 0)); if (_hEventComplete) ThdVerify(CloseHandle(_hEventComplete)); if (_hEventEnableSecondTierThreads) ThdVerify(CloseHandle(_hEventEnableSecondTierThreads)); if (_hSemNSpare) ThdVerify(CloseHandle(_hSemNSpare)); DeleteCriticalSection(&_csSpare); LocalFree(_ppSpareTasks); ThdInterlockedDecrement(&g_cPools); ThdAssert(_cRefs == 0); ThdAssert((_cSpare == 0 && _cPoolThreads == 0) || _cSpare == _cPoolThreads); } //+------------------------------------------------------------------- // // Member: CThreadPool::AssignCommandToThread // // Synopsis: Finds or creates a thread to execute the passed in // command. Takes ownership of the command. // // Arguments: [pCommand] -- the command to pass to a thread. // // [tier] -- the tier of the thread, if -1 then this is // the UI command that will be notified by a call to // // // // Returns: MK_E_EXCEEDEDDEADLINE if pool is being closed down. // E_OUTOFMEMORY // // S_OK if command has been transferred to a thread // // Algorithm: if this is a tier 2 command then wait until // EnableSecondTierThreads has been called. // wait on the nspare semaphore which when acquired means // that either 1 or more threads are spare, or a thread // can be created. // //-------------------------------------------------------------------- HRESULT CThreadPool::AssignCommandToThread(CCommand *pCommand, int tier) { HRESULT hr = S_OK; CTask *pTask; if (_fTerminatePool || _fAllDying) { return(MK_E_EXCEEDEDDEADLINE); } if (tier > 1 ) // ensure that search step threads get created before // we reach limit on # of threads. { ThdWaitForSingleObject("AssignCommandToThread::Second Tier", _hEventEnableSecondTierThreads, INFINITE); //wait on enable second tier threads } // this semaphore lets us in if there is a task in the spare list // or if we still are allowed to create more threads ThdWaitForSingleObject( "AssignCommandToThread::_hSemNSpare", _hSemNSpare, INFINITE ); EnterCriticalSection(&_csSpare); if (_cPoolThreads < _cMaxThreads && _cSpare == 0) { // will come through here at most _cMaxThreads times // // wait for CoUninitialize abort or all threads mutex // hr = _refAllThreads.CreateAndRecordThread(&pTask, this); if (hr == MK_E_EXCEEDEDDEADLINE) { _fTerminatePool = TRUE; } if (hr == S_OK) { _cPoolThreads++; ThdPrint("%08X, AssignCommandToThread created a thread, " "_cPoolThreads=%d\n", GetCurrentThreadId(), _cPoolThreads); } if (_cPoolThreads < _cMaxThreads) ReleaseSemaphore(_hSemNSpare, 1, NULL); } else { ThdAssert(_cSpare != 0); pTask = _ppSpareTasks[--_cSpare]; ThdPrint("%08X, AssignCommandToThread recycled a thread, _cSpare=%d\n", GetCurrentThreadId(), _cSpare); } if (hr == S_OK) { if (tier == -1) { _pUiCommand = pCommand; } pTask->SetCommand(pCommand); pTask->WakeThread(); //wake thread by releasing wait for work mutex } LeaveCriticalSection( &_csSpare ); return(hr); } //+------------------------------------------------------------------- // // Member: CThreadPool::WaitForSuccessOrCompletion // // Synopsis: Wait until either there has been a successful command or // all threads have become spare (i.e. all commands failed) // At the end of the wait, tell all threads in pool to // stop creating work and therefore become spare and kill the // pool. // // Algorithm: Wait on _hEventComplete // //-------------------------------------------------------------------- VOID CThreadPool::WaitForSuccessOrCompletion() { DWORD i; DebugMsg(DM_TRACE, TEXT("In WaitForSuccessOrCompletion")); if (_cPoolThreads != 0) { ThdWaitForSingleObject("WaitForSuccessOrCompletion::_hEventComplete", _hEventComplete, INFINITE); } // signal all threads to exit // // Three cases: // 1) if the wait timed out, then all the search threads // will exit (because _fTerminatePool is set TRUE), leaving the Ui thread. // This flag is checked by downlevel searches too. When all but the Ui thread // have exitted, the pool will inform the Ui to close by a call // from CThreadPool::PoolThread to CCancelWindowCommand::CancelFromPool. // 2) if the search was successful by another thread, the pool will // call from CThreadPool::SetCompletionStatus to CCancelWindowCommand:: // CancelFromPool. This will cause the dialog to disappear. // 3) if the search was cancelled by the user using the browse dialog then // CCancelWindow::message IDCANCEL calls CThreadPool::StopSearchFromUI which // causes the pool to terminate all search threads, and when the last // thread (the ui thread) exits after GetFileNameFromBrowse returns and // EndDialog is called, the UI thread itself dies too. This causes the // whole pool to go away. // _fTerminatePool = TRUE; DebugMsg(DM_TRACE, TEXT("Out WaitForSuccessOrCompletion\n")); } //+------------------------------------------------------------------- // // Member: CThreadPool::SetCompletionStatus // // Synopsis: Transfer ownership of the (successful) command to the pool and // wake up any // // Arguments: [ppCommand] -- pointer to the buffer containing the pointer // to the successful command. // // Notes: The successful command can only be set once. // If the command is transferred from the caller to the pool // then *ppCommand is set to NULL, otherwise *ppCommand // is left alone. // //-------------------------------------------------------------------- VOID CThreadPool::SetCompletionStatus(CCommand *pCommand) { ThdPrint("%08X, SetCompletionStatus(pCommand = %08X\n", GetCurrentThreadId(), pCommand); if (_pUiCommand != NULL && pCommand != _pUiCommand) { ThdPrint("%08X, SetCompletionStatus calls _pUiCommand->CancelFromPool()\n", GetCurrentThreadId()); _pUiCommand->CancelFromPool(); } SetEvent(_hEventComplete); _fEventComplete = TRUE; } //+------------------------------------------------------------------- // // Member: CThreadPool::PoolThread // // Synopsis: Add this thread back to the pool as a spare thread, // ready to do work. If all threads are now spare, then // kill off the pool and remove all threads from the // referenced global thread set. // // Arguments: [pTask] -- pointer to the task of this thread. // // Algorithm: Stuff pTask into the array of spare threads and // then add one to the semaphore to release a thread // to read it in AssignCommandToThread. Of course, do // this table manipulation in the critical section. // // If all threads are spare then there are no more threads // that can create work and therefore the pool has done // its job and should be terminated. // // Notes: // //-------------------------------------------------------------------- VOID CThreadPool::PoolThread(CTask *pTask) { ThdAssert(!_fAllDying); ThdAssert(pTask != NULL); if (MK_E_EXCEEDEDDEADLINE == TimeoutExpired(_dwTickCountDeadline)) { _fTerminatePool = TRUE; } EnterCriticalSection(&_csSpare); _ppSpareTasks[_cSpare++] = pTask; ReleaseSemaphore(_hSemNSpare, 1, NULL); ThdPrint("%08X, PoolThread pools a thread, _cSpare=%d\n", GetCurrentThreadId(), _cSpare); if (_cSpare == _cPoolThreads) { // obviously if we have all spare threads then there can be // no more work to do and no one to generate more work ThdPrint("%08X, PoolThread detects all threads spare, " "_cPoolThreads=_cSpare=%d\n", GetCurrentThreadId(), _cSpare); SetEvent(_hEventComplete); // set "all threads done searching event" _fAllDying = TRUE; LeaveCriticalSection(&_csSpare); _refAllThreads.KillTasks(_ppSpareTasks, _cSpare, pTask); // at this point 'this' has been deleted // and this and all other threads in the pool should have terminated ThdAssert(FALSE); } else if (_cSpare == _cPoolThreads-1 && _pUiCommand != NULL) { // if there is one running thread and we have UI operational // then the one thread is the UI thread: we should cancel // UI since the search is completed (either due to success OR // to all other threads having searched everywhere they should've) ThdPrint("%08X, PoolThread calls _pUiCommand->CancelFromPool()\n", GetCurrentThreadId()); _pUiCommand->CancelFromPool(); } LeaveCriticalSection(&_csSpare); } //+------------------------------------------------------------------- // // Member: CThreadPool::TerminatePool // // Synopsis: Causes no more work to be assigned to threads and thus // all threads will become idle, ready for pool // termination wwhen the final thread becomes idle. // // Notes: Called by UI command to stop the search. // //-------------------------------------------------------------------- VOID CThreadPool::TerminatePool() { _fTerminatePool = TRUE; } //+------------------------------------------------------------------- // // Function: StartTaskThread // // Synopsis: Called by CreateThread as the thread routine. // // Arguments: [pvTask] -- this pointer for the CTask object that // is associated with this thread. // // Returns: Never returns. // //-------------------------------------------------------------------- DWORD WINAPI StartTaskThread(VOID *pvTask) { ((CTask*)pvTask)->DoTask(); ThdAssert(0); return(0); } //+------------------------------------------------------------------- // // Member: CTask::CTask // // Synopsis: Constructor for object that represents the thread which // has its handle stored in _hThread. // // Arguments: [phr] -- set to S_OK if successful, otherwise untouched. // // Notes: Initialize members. Careful to create thread last! // //-------------------------------------------------------------------- CTask::CTask(HRESULT *phr) { DWORD dwThread; _pPool = NULL; _pCommand = NULL; _pNext = NULL; _fAbort = FALSE; _hSemWaitForWork = CreateSemaphore(NULL, 0, 2, NULL); if (_hSemWaitForWork != NULL) { _hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StartTaskThread, this, 0, &dwThread); if (_hThread != NULL) { ThdInterlockedIncrement(&g_cThreads); *phr = S_OK; } } else { _hThread = NULL; } } //+------------------------------------------------------------------- // // Member: CTask::~CTask // // Synopsis: Destructor... thread is signalled, just delete the data. // //-------------------------------------------------------------------- CTask::~CTask() { if (_hThread != NULL) ThdVerify(CloseHandle(_hThread)); if (_hSemWaitForWork != NULL) ThdVerify(CloseHandle(_hSemWaitForWork)); if (_pPool != NULL) _pPool->Release(); delete _pCommand; } //+------------------------------------------------------------------- // // Member: CTask::DoTask // // Synopsis: Main loop for any thread that is in a pool. // Waits for something to do. // Will call ExitThread if told to abort (_fAbort) // // Notes: This is the only place that threads exit, except // for the last thread in a pool which can exit // in PoolThread calling KillTasks // //-------------------------------------------------------------------- VOID CTask::DoTask() { while (TRUE) { ThdWaitForSingleObject("DoTask", _hSemWaitForWork, INFINITE); if (_fAbort) { ThdPrint("%08X, DoTask: Thread Exit\n", GetCurrentThreadId()); ThdInterlockedDecrement(&g_cThreads); ExitThread(0); } _pCommand->DoCommand(); _pPool->PoolThread(this); } } //+------------------------------------------------------------------- // // Member: CTask::AbortThread // // Synopsis: Tell the thread to abort and wait until it has. // //-------------------------------------------------------------------- VOID CTask::AbortThread() { _fAbort = TRUE; ReleaseSemaphore(_hSemWaitForWork, 1, NULL); DWORD index=ThdWaitForSingleObject("AbortThread", _hThread, INFINITE); ThdAssert(index == WAIT_OBJECT_0); } //+------------------------------------------------------------------- // // Member: CCommand::CCommand // // Synopsis: Common constructor to all commands. // // Arguments: [ptszParam] -- a handy string parameter that can be used // by the derived classes. // [phr] -- set on error, untouched otherwise. // //-------------------------------------------------------------------- CCommand::CCommand(const TCHAR *ptszParam, HRESULT *phr) { if (ptszParam != NULL) { _ptszParam = (TCHAR*)LocalAlloc(LMEM_FIXED, SIZEOF(TCHAR) * (lstrlen(ptszParam)+1)); if (_ptszParam != NULL) { lstrcpy(_ptszParam, ptszParam); } else { *phr = E_OUTOFMEMORY; } } else { _ptszParam = NULL; } } #endif