//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1995 // // File: thrdcomm.cxx // // Contents: Implementation of the CThreadComm class // //---------------------------------------------------------------------------- #include "headers.hxx" DeclareTagOther(tagDontKillThread, "MTScript", "Dont Terminate Threads on Shutdown"); //+--------------------------------------------------------------------------- // // CThreadComm class // // Handles communication between threads. // //---------------------------------------------------------------------------- CThreadComm::CThreadComm() { Assert(_hCommEvent == NULL); Assert(_hThreadReady == NULL); Assert(_hThread == NULL); Assert(_pMsgData == NULL); } CThreadComm::~CThreadComm() { MESSAGEDATA *pMsg; if (_hThread) CloseHandle(_hThread); if (_hCommEvent) CloseHandle(_hCommEvent); if (_hThreadReady) CloseHandle(_hThreadReady); if (_hResultEvt) CloseHandle(_hResultEvt); while (_pMsgData) { pMsg = _pMsgData->pNext; delete _pMsgData; _pMsgData = pMsg; } } //+--------------------------------------------------------------------------- // // Member: CThreadComm::Init, public // // Synopsis: Initializes the class. Must be called on all instances before // using. // //---------------------------------------------------------------------------- BOOL CThreadComm::Init() { _hCommEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!_hCommEvent) goto Error; _hThreadReady = CreateEvent(NULL, TRUE, FALSE, NULL); if (!_hThreadReady) goto Error; _hResultEvt = CreateEvent(NULL, TRUE, FALSE, NULL); if (!_hResultEvt) goto Error; return TRUE; Error: ErrorPopup(L"CThreadComm::Init"); return FALSE; } //+--------------------------------------------------------------------------- // // Member: CThreadComm::SendHelper, public // // Synopsis: Send or post the given message to the thread which owns // this class. // // Arguments: [md] -- Message to send // [pvData] -- Associated data with message. // [cbData] -- Size of info that [pvData] points to. // [fSend] -- TRUE if we're doing a send, FALSE if it's a post // [hResultEvt] -- Event handle to signal when the result is ready // //---------------------------------------------------------------------------- DWORD CThreadComm::SendHelper(THREADMSG mt, void * pvData, DWORD cbData, BOOL fSend, HANDLE hResultEvt) { DWORD dwRet = 0; MESSAGEDATA *pMsg = NULL, *pMsgLoop; AssertSz(!pvData || cbData != 0, "Invalid params to PostToThread"); pMsg = new MESSAGEDATA; if (!pMsg) goto Error; pMsg->pNext = NULL; pMsg->tmMessage = mt; pMsg->dwResult = 0; pMsg->hResultEvt = hResultEvt; if (pvData) { AssertSz(cbData <= MSGDATABUFSIZE, "Data is too big!"); pMsg->cbData = cbData; memcpy(pMsg->bData, pvData, cbData); } else { pMsg->cbData = 0; } { LOCK_LOCALS(this); if (!fSend) { // // Stick the new message at the end so we get messages FIFO // pMsgLoop = _pMsgData; while (pMsgLoop && pMsgLoop->pNext) { pMsgLoop = pMsgLoop->pNext; } if (!pMsgLoop) { _pMsgData = pMsg; } else { pMsgLoop->pNext = pMsg; } } else { // Set dwResult to indicate we're expecting a result pMsg->dwResult = 1; // // Put sent messages at the front to minimize potential deadlocks // pMsg->pNext = _pMsgData; _pMsgData = pMsg; Assert(hResultEvt); ResetEvent(hResultEvt); } } SetEvent(_hCommEvent); if (fSend) { HANDLE ahEvents[2]; ahEvents[0] = hResultEvt; ahEvents[1] = _hThread ; DWORD dwWait = WaitForMultipleObjects(2, ahEvents, FALSE, 50000); switch(dwWait) { case WAIT_OBJECT_0: // OK, this is good break; default: case WAIT_OBJECT_0 + 1: case WAIT_TIMEOUT: AssertSz(FALSE, "SendToThread never responded!"); // // This causes a memory leak, but it's better than a crash. What // we really need to do is remove the message from the queue. // return 0; } dwRet = pMsg->dwResult; delete pMsg; } return dwRet; Error: ErrorPopup(L"CThreadComm::PostToThread - out of memory"); return dwRet; } //+--------------------------------------------------------------------------- // // Member: CThreadComm::PostToThread, public // // Synopsis: Post the given message to the thread which owns pTarget. // // Arguments: [md] -- Message to send // [pvData] -- Associated data with message. // [cbData] -- Size of info that [pvData] points to. // //---------------------------------------------------------------------------- void CThreadComm::PostToThread(CThreadComm *pTarget, THREADMSG mt, void * pvData, DWORD cbData) { (void)pTarget->SendHelper(mt, pvData, cbData, FALSE, NULL); } //+--------------------------------------------------------------------------- // // Member: CThreadComm::SendToThread, public // // Synopsis: Send the given message to the thread which owns this class. // // Arguments: [md] -- Message to send // [pvData] -- Associated data with message. // [cbData] -- Size of info that [pvData] points to. // //---------------------------------------------------------------------------- DWORD CThreadComm::SendToThread(CThreadComm *pTarget, THREADMSG mt, void * pvData, DWORD cbData) { DWORD dwRet; VERIFY_THREAD(); Assert(sizeof(_fInSend) == 4); // We are relying on atomic read/writes // because multiple threads are accessing // this variable. Assert(!_fInSend); _fInSend = TRUE; if (pTarget->_fInSend) { // // Somebody is trying to send to us while we're sending to someone else. // This is potentially a deadlock situation! First, wait and see if it // resolves, then if it doesn't, assert and bail out! // TraceTag((tagError, "Perf Hit! Avoiding deadlock situation!")); Sleep(100); // Arbitrary 100 ms if (pTarget->_fInSend) { AssertSz(FALSE, "Deadlock - SendToThread called on object doing a send!"); _fInSend = FALSE; return 0; } } dwRet = pTarget->SendHelper(mt, pvData, cbData, TRUE, _hResultEvt); _fInSend = FALSE; return dwRet; } //+--------------------------------------------------------------------------- // // Member: CThreadComm::GetNextMsg, public // // Synopsis: Retrieves the next message waiting for this thread. // // Arguments: [md] -- Place to put message type. // [pData] -- Associated data for message. Must be allocated // memory of size MSGDATABUFSIZE // [pcbData] -- Size of info in *pData is returned here. // // Returns: TRUE if a valid message was returned. FALSE if there are no // more messages. // //---------------------------------------------------------------------------- BOOL CThreadComm::GetNextMsg(THREADMSG *mt, void * pData, DWORD *pcbData) { MESSAGEDATA *pMsg; BOOL fRet = TRUE; VERIFY_THREAD(); LOCK_LOCALS(this); AssertSz(!_pMsgReply, "Sent message not replied to!"); pMsg = _pMsgData; if (pMsg) { _pMsgData = pMsg->pNext; *mt = pMsg->tmMessage; *pcbData = pMsg->cbData; memcpy(pData, pMsg->bData, pMsg->cbData); // // If the caller is not expecting a reply, delete the message. If he is, // then the caller will free the memory. // if (pMsg->dwResult == 0) { delete pMsg; } else { _pMsgReply = pMsg; } } else if (!_pMsgData) { ResetEvent(_hCommEvent); fRet = FALSE; } return fRet; } //+--------------------------------------------------------------------------- // // Member: CThreadComm::Reply, public // // Synopsis: Call this to send the result of a SendToThread call back to // the calling thread. // // Arguments: [dwReply] -- Result to send back. // //---------------------------------------------------------------------------- void CThreadComm::Reply(DWORD dwReply) { VERIFY_THREAD(); Assert(_pMsgReply); _pMsgReply->dwResult = dwReply; SetEvent(_pMsgReply->hResultEvt); _pMsgReply = NULL; } //+--------------------------------------------------------------------------- // // Member: MessageEventPump, public // // Synopsis: Empties our message queues (both windows' and our private // threadcomm queue) // // Arguments: // [hEvent] -- event handle to wait for // // Returns: // WAIT_ABANDONED: An event occurred which is causing this thread to // terminate. The caller should clean up and finish // what it's doing. // WAIT_OBJECT_0: If one (or all if fAll==TRUE) of the passed-in // event handles became signaled. The index of the // signaled handle is added to MEP_EVENT_0. Returned // only if one or more event handles were passed in. // //---------------------------------------------------------------------------- DWORD MessageEventPump(HANDLE hEvent) { MSG msg; DWORD dwRet; DWORD mepReturn = 0; do { // // Purge out all window messages (primarily for OLE's benefit). // while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { return WAIT_ABANDONED; } TranslateMessage(&msg); DispatchMessage(&msg); } dwRet = MsgWaitForMultipleObjects(1, &hEvent, FALSE, INFINITE, QS_ALLINPUT); if (dwRet == WAIT_OBJECT_0) { mepReturn = dwRet; break; } else if (dwRet == WAIT_OBJECT_0 + 1) { // // A windows message came through. It will be handled at the // top of the loop. // } else if (dwRet == WAIT_FAILED || dwRet == WAIT_ABANDONED) { TraceTag((tagError, "WaitForMultipleObjects failure (%d)", GetLastError())); AssertSz(FALSE, "WaitForMultipleObjects failure"); mepReturn = dwRet; break; } } while (true); return mepReturn; } //+--------------------------------------------------------------------------- // // Member: CThreadComm::StartThread, public // // Synopsis: Starts a new thread that owns this CThreadComm instance. // //---------------------------------------------------------------------------- HRESULT CThreadComm::StartThread(void * pvParams) { if (!Init()) { AssertSz(FALSE, "Failed to initialize new class."); return E_FAIL; // TODO: need to change Init() to return HRESULT } ResetEvent(_hThreadReady); // // Create suspended because we need to set member variables before it // gets going. // _hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CThreadComm::TempThreadRoutine, (LPVOID)this, CREATE_SUSPENDED, &_dwThreadId); if (_hThread == NULL) { long e = GetLastError(); return HRESULT_FROM_WIN32(e); } _pvParams = pvParams; _hrThread = S_OK; // // Everything's set up - get it going! // ResumeThread(_hThread); // // Wait for the new thread to say it's ready... // MessageEventPump(_hThreadReady); // This is neccessary to allow the new thread to retrieve the script parameter with GetInterfaceFromGlobal(). // On failure, wait for the thread to exit before returning. if (FAILED(_hrThread)) { WaitForSingleObject(_hThread, INFINITE); return _hrThread; } return S_OK; } //+--------------------------------------------------------------------------- // // Member: CThreadComm::TempThreadRoutine, public // // Synopsis: Static member given to CreateThread. Just a stub that calls // the real thread routine. // //---------------------------------------------------------------------------- DWORD CThreadComm::TempThreadRoutine(LPVOID pvParam) { CThreadComm *pThis = (CThreadComm*)pvParam; AssertSz(pThis, "Bad arg passed to CThreadComm::TempThreadRoutine"); return pThis->ThreadMain(); } //+--------------------------------------------------------------------------- // // Member: CThreadComm::SetName, public // // Synopsis: On a debug build, sets the thread name so the debugger // lists the threads in an understandable manner. // // Arguments: [pszName] -- Name to set thread to. // //---------------------------------------------------------------------------- void CThreadComm::SetName(LPCSTR pszName) { #if DBG == 1 THREADNAME_INFO info = { 0x1000, pszName, _dwThreadId }; __try { RaiseException(0x406d1388, 0, sizeof(info) / sizeof(DWORD), (DWORD *)&info); } __except(EXCEPTION_CONTINUE_EXECUTION) { } #endif } //+--------------------------------------------------------------------------- // // Member: CThreadComm::Shutdown, public // // Synopsis: Forces the thread containing the given instance of the // ThreadComm object to shutdown. // // Arguments: [pTarget] -- Object to shutdown // // Returns: TRUE if it shutdown normally, FALSE if it had to be killed. // //---------------------------------------------------------------------------- BOOL CThreadComm::Shutdown(CThreadComm *pTarget) { BOOL fReturn = TRUE; DWORD dwTimeout = (IsTagEnabled(tagDontKillThread)) ? INFINITE : 5000; DWORD dwCode; GetExitCodeThread(pTarget->hThread(), &dwCode); // Is client already dead? if (dwCode != STILL_ACTIVE) return TRUE; // // Sending the PLEASEEXIT message will flush all other messages out, // causing any unsent data to be sent before closing the pipe. // PostToThread(pTarget, MD_PLEASEEXIT); // Wait 5 seconds for our thread to terminate and then kill it. if (WaitForSingleObject(pTarget->hThread(), dwTimeout) == WAIT_TIMEOUT) { TraceTag((tagError, "Terminating thread for object %x...", this)); AssertSz(FALSE, "I'm being forced to terminate a thread!"); TerminateThread(pTarget->hThread(), 1); fReturn = FALSE; } return fReturn; }