/*++ Copyright (c) 2000 Microsoft Corporation Module Name: vststmsg.cxx Abstract: Implementation of test message classes for the server Brian Berkowitz [brianb] 05/22/2000 TBD: Revision History: Name Date Comments brianb 05/22/2000 Created ssteiner 06/07/2000 Split client and server portions into two files. vststmsgclient.cxx contains the client portion. --*/ #include #include #include void LogUnexpectedFailure(LPCWSTR wsz, ...); /*++ Routine Description: This needs to be run once on the server side to install all of the message handlers. Arguments: None Return Value: --*/ static void InstallServerMsgHandlers() { g_msgTypes[VSTST_MT_TEXT].pfnHandler = CVsTstMsgHandlerRoutines::PrintMessage; g_msgTypes[VSTST_MT_IMMEDIATETEXT].pfnHandler = CVsTstMsgHandlerRoutines::PrintMessage; g_msgTypes[VSTST_MT_FAILURE].pfnHandler = CVsTstMsgHandlerRoutines::HandleFailure; g_msgTypes[VSTST_MT_OPERATIONFAILURE].pfnHandler = CVsTstMsgHandlerRoutines::HandleOperationFailure; g_msgTypes[VSTST_MT_UNEXPECTEDEXCEPTION].pfnHandler = CVsTstMsgHandlerRoutines::HandleUnexpectedException; g_msgTypes[VSTST_MT_SUCCESS].pfnHandler = CVsTstMsgHandlerRoutines::HandleSuccess; } CVsTstMsgHandler::CVsTstMsgHandler( IN LPCWSTR pwszLogFileName ) : m_bcsQueueInitialized(false), m_pmsgFirst(NULL), m_pmsgLast(NULL), m_cbMaxMsgLength(0), m_hThreadReader(NULL), m_hThreadWorker(NULL), m_hevtWorker(NULL), m_hevtReader(NULL), m_bReadEnabled(false), m_bTerminateWorker(false), m_bTerminateReader(false), m_pipeList(NULL), m_bcsPipeListInitialized(false), m_cNtLog( pwszLogFileName ) { // // Initialize the general message types array and install the message // handlers. // InitMsgTypes(); InstallServerMsgHandlers(); } // free data allocated by the class void CVsTstMsgHandler::FreeData() { if (m_bcsQueueInitialized) { m_csQueue.Term(); m_bcsQueueInitialized = false; } if (m_bcsPipeListInitialized) { m_csPipeList.Term(); m_bcsPipeListInitialized = false; } while(m_pmsgFirst) { VSTST_MSG_HDR *pmsgNext = m_pmsgFirst->pmsgNext; delete m_pmsgFirst; m_pmsgFirst = pmsgNext; } if (m_hThreadWorker) { CloseHandle(m_hThreadWorker); m_hThreadWorker = NULL; } if (m_hevtWorker) { CloseHandle(m_hevtWorker); m_hevtWorker = NULL; } if (m_hevtReader) { CloseHandle(m_hevtReader); m_hevtReader = NULL; } } CVsTstMsgHandler::~CVsTstMsgHandler() { ForceTermination(); FreeData(); } // initailize message handler and worker thread HRESULT CVsTstMsgHandler::Initialize(UINT cbMaxMsg) { m_cbMaxMsgLength = cbMaxMsg; try { m_csQueue.Init(); m_bcsQueueInitialized = true; m_csPipeList.Init(); m_bcsPipeListInitialized = true; } catch(...) { return E_UNEXPECTED; } HRESULT hr = S_OK; m_hevtWorker = CreateEvent(NULL, TRUE, FALSE, NULL); if (m_hevtWorker == NULL) goto _ErrExit; m_hevtReader = CreateEvent(NULL, TRUE, FALSE, NULL); if (m_hevtReader == NULL) goto _ErrExit; DWORD tid; m_hThreadWorker = CreateThread ( NULL, 64*1024, StartWorkerThread, this, 0, &tid ); if (m_hThreadWorker == NULL) goto _ErrExit; return S_OK; _ErrExit: hr = HRESULT_FROM_WIN32(GetLastError()); FreeData(); return hr; } // adjust message pointers bool CVsTstMsgHandler::AdjustPointers(VSTST_MSG_HDR *phdr) { VSTST_MSG_TYPE_TABLE *pType = &g_msgTypes[phdr->type]; BYTE *pb = phdr->rgb; VOID **ppv = (VOID **) (pb + pType->cbFixed); pb = (BYTE *) (ppv + pType->cVarPtr); for(unsigned iVarPtr = 0; iVarPtr < pType->cVarPtr; iVarPtr++, ppv++) { *ppv = pb; size_t cb; switch(pType->pointerTypes[iVarPtr]) { default: return false; case VSTST_VPT_BYTE: cb = *(UINT *) pb; break; case VSTST_VPT_ANSI: cb = strlen((char *) pb) + 1; break; case VSTST_VPT_UNICODE: cb = (wcslen((WCHAR *) pb) + 1) * sizeof(WCHAR); break; } // align to pointer boundary cb = (cb + sizeof(PVOID) - 1) & ~(sizeof(PVOID) - 1); pb += cb; } return true; } // process message immediately bool CVsTstMsgHandler::ProcessMsgImmediate(VSTST_MSG_HDR *phdr) { if (!AdjustPointers(phdr)) return false; VSTST_MSG_TYPE_TABLE *pType = &g_msgTypes[phdr->type]; try { pType->pfnHandler(phdr, &m_cNtLog); } catch(...) { return false; } return true; } // queue message for worker thrad bool CVsTstMsgHandler::QueueMsg(VSTST_MSG_HDR *phdr) { BYTE *pbMsg = new BYTE[phdr->cbMsg]; if (pbMsg == NULL) // can't allocate message, wait for queue to of messages // to complete and then process messages serially { WaitForQueueToComplete(); return ProcessMsgImmediate(phdr); } memcpy(pbMsg, phdr, phdr->cbMsg); VSTST_MSG_HDR *phdrT = (VSTST_MSG_HDR *) pbMsg; if (!AdjustPointers(phdrT)) return false; m_csQueue.Lock(); if (m_pmsgLast == NULL) { VSTST_ASSERT(m_pmsgFirst == NULL); m_pmsgLast = m_pmsgFirst = phdrT; SetEvent(m_hevtWorker); } else { VSTST_ASSERT(m_pmsgLast->pmsgNext == NULL); m_pmsgLast->pmsgNext = phdrT; // replace last element on queue m_pmsgLast = phdrT; } m_csQueue.Unlock(); return true; } // execute items on the work queue bool CVsTstMsgHandler::DoWork() { // Add this thread as a participant m_cNtLog.AddParticipant(); // For now start the one and only variation m_cNtLog.StartVariation( L"VssTestController" ); while(TRUE) { // wait for something to get on the work queue or for 1 second if (WaitForSingleObject(m_hevtWorker, 1000) == WAIT_FAILED) { // Log severe error m_cNtLog.Log( eSevLev_Severe, L"CVsTstMsgHandler::DoWork, WaitForSingleObject returned WAIT_FAILED, dwRet: %d", ::GetLastError() ); // End the variation m_cNtLog.EndVariation(); // Remove the thread as a participant m_cNtLog.RemoveParticipant(); return false; } while(TRUE) { // lock queue m_csQueue.Lock(); // check whether queue is empty if (m_pmsgFirst == NULL) { VSTST_ASSERT(m_pmsgLast == NULL); // check whether we should terminate the thread if (m_bTerminateWorker) { // terminate thread m_csQueue.Unlock(); // End the variation m_cNtLog.EndVariation(); // Remove the thread as a participant m_cNtLog.RemoveParticipant(); return true; } // setup to wait again ResetEvent(m_hevtWorker); m_csQueue.Unlock(); break; } // pull first message off of queue VSTST_MSG_HDR *phdr = m_pmsgFirst; // move head of queue to next element m_pmsgFirst = m_pmsgFirst->pmsgNext; // is queue now empty if (m_pmsgFirst == NULL) { VSTST_ASSERT(m_pmsgLast == phdr); // set tail of queue to null m_pmsgLast = NULL; } // unlock queue before executing item m_csQueue.Unlock(); // execute item VSTST_MSG_TYPE_TABLE *pType = &g_msgTypes[phdr->type]; try { pType->pfnHandler(phdr, &m_cNtLog ); } catch(...) { // Log severe error m_cNtLog.Log( eSevLev_Severe, L"CVsTstMsgHandler::DoWork, caught unexpected exception from message handler" ); // End the variation m_cNtLog.EndVariation(); // Remove the thread as a participant m_cNtLog.RemoveParticipant(); return false; } } } return true; } // terminate worker thread, waiting for all work to complete void CVsTstMsgHandler::WaitForQueueToComplete() { m_bTerminateWorker = true; if (WaitForSingleObject(m_hThreadWorker, INFINITE) == WAIT_FAILED) { // polling way to wait if we wait fails. Note that we usually // would only expect to get here in stress situations while(TRUE) { m_csQueue.Lock(); if (m_pmsgFirst == NULL) { m_csQueue.Unlock(); break; } m_csQueue.Unlock(); Sleep(100); } } } DWORD CVsTstMsgHandler::StartWorkerThread(VOID *pv) { CVsTstMsgHandler *pHandler = (CVsTstMsgHandler *) pv; try { pHandler->DoWork(); } catch(...) { LogUnexpectedFailure(L"Worker thread unexpectedly terminated"); } return 0; } void CVsTstMsgHandler::StartProcessingMessages() { m_bReadEnabled = true; SetEvent(m_hevtReader); } void CVsTstMsgHandler::StopProcessingMessages() { ResetEvent(m_hevtReader); m_bReadEnabled = false; } void CVsTstMsgHandler::ForceTermination() { m_bReadEnabled = false; m_bTerminateWorker = true; m_bTerminateReader = true; SetEvent(m_hevtReader); if (m_hThreadWorker) { DWORD dwErr = WaitForSingleObject(m_hThreadWorker, 5000); if (dwErr == WAIT_FAILED || dwErr == WAIT_TIMEOUT) // force termination of worker thread TerminateThread(m_hThreadWorker, 1); CloseHandle(m_hThreadWorker); m_hThreadWorker = NULL; } m_csPipeList.Lock(); while(m_pipeList != NULL) { CVsTstPipe *pipe = m_pipeList; HANDLE hThread = pipe->m_hThreadReader; pipe->m_hThreadReader = NULL; m_csPipeList.Unlock(); m_pipeList->ForceTermination(hThread); m_csPipeList.Lock(); if (m_pipeList == pipe) delete m_pipeList; } m_csPipeList.Unlock(); } // launch a pipe reader thread HRESULT CVsTstMsgHandler::LaunchReader() { CVsTstPipe *pipe = new CVsTstPipe(this); if (pipe == NULL) return E_OUTOFMEMORY; HRESULT hr = pipe->Initialize(m_cbMaxMsgLength); if (FAILED(hr)) { delete pipe; return hr; } return S_OK; } // link pipe into pipe list void CVsTstMsgHandler::LinkPipe(CVsTstPipe *pipe) { VSTST_ASSERT(!pipe->m_bLinked); m_csPipeList.Lock(); pipe->m_prev = NULL; pipe->m_next = m_pipeList; if (m_pipeList) { VSTST_ASSERT(m_pipeList->m_prev == NULL); m_pipeList->m_prev = pipe; } m_pipeList = pipe; m_csPipeList.Unlock(); pipe->m_bLinked = true; } // unlink pipe from pipe list void CVsTstMsgHandler::UnlinkPipe(CVsTstPipe *pipe) { VSTST_ASSERT(pipe->m_bLinked); m_csPipeList.Lock(); if (pipe->m_prev == NULL) { VSTST_ASSERT(m_pipeList == pipe); m_pipeList = pipe->m_next; if (m_pipeList) { VSTST_ASSERT(m_pipeList->m_prev == pipe); m_pipeList->m_prev = NULL; } } else { VSTST_ASSERT(pipe->m_prev->m_next == pipe); pipe->m_prev->m_next = pipe->m_next; if (pipe->m_next) { VSTST_ASSERT(pipe->m_next->m_prev == pipe); pipe->m_next->m_prev = pipe->m_prev; } } m_csPipeList.Unlock(); pipe->m_bLinked = false; } // constructor for a pipe CVsTstPipe::CVsTstPipe(CVsTstMsgHandler *pHandler) : m_hPipe(NULL), m_hevtOverlapped(NULL), m_rgbMsg(NULL), m_cbMsg(0), m_hThreadReader(NULL), m_bLinked(NULL), m_pHandler(pHandler), m_bConnected(false) { } // destructor for a pipe CVsTstPipe::~CVsTstPipe() { // unlink pipe from list if linked if (m_bLinked) { VSTST_ASSERT(m_pHandler); m_pHandler->UnlinkPipe(this); } FreeData(); } // initailize message handler and worker thread HRESULT CVsTstPipe::Initialize(UINT cbMaxMsg) { HRESULT hr = S_OK; // create pipe m_hPipe = CreateNamedPipe ( s_wszPipeName, FILE_FLAG_OVERLAPPED|PIPE_ACCESS_INBOUND, PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, cbMaxMsg, 100, NULL ); if (m_hPipe == INVALID_HANDLE_VALUE) return HRESULT_FROM_WIN32(GetLastError()); // create message buffer m_cbMsg = cbMaxMsg; m_rgbMsg = new BYTE[m_cbMsg]; if (m_rgbMsg == NULL) { hr = E_OUTOFMEMORY; goto _ErrCleanup; } // create overlapped read event m_hevtOverlapped = CreateEvent(NULL, TRUE, FALSE, NULL); if (m_hevtOverlapped == NULL) goto _ErrExit; // create reader thread DWORD tid; m_hThreadReader = CreateThread ( NULL, 256*1024, StartReaderThread, this, 0, &tid ); if (m_hThreadReader == NULL) goto _ErrExit; // link pipe into list m_pHandler->LinkPipe(this); return S_OK; _ErrExit: hr = HRESULT_FROM_WIN32(GetLastError()); _ErrCleanup: FreeData(); return hr; } // free data allocated by the class void CVsTstPipe::FreeData() { delete m_rgbMsg; m_rgbMsg = NULL; if (m_hPipe != INVALID_HANDLE_VALUE) { // disconnect pipe if connected if (m_bConnected) { DisconnectNamedPipe(m_hPipe); m_bConnected = false; } CloseHandle(m_hPipe); m_hPipe = NULL; } if (m_hThreadReader) { CloseHandle(m_hThreadReader); m_hThreadReader = NULL; } if (m_hevtOverlapped) { CloseHandle(m_hevtOverlapped); m_hevtOverlapped = NULL; } } void CVsTstPipe::ForceTermination(HANDLE hThread) { // thread should already be terminated DWORD dwErr = WaitForSingleObject(hThread, 5000); if (dwErr == WAIT_FAILED || dwErr == WAIT_TIMEOUT) // force termination of thread TerminateThread(hThread, 1); CloseHandle(hThread); } // setup overlapped I/O structure used for reading data // from the pipe void CVsTstPipe::SetupOverlapped() { VSTST_ASSERT(m_hevtOverlapped); ResetEvent(m_hevtOverlapped); memset(&m_overlap, 0, sizeof(m_overlap)); m_overlap.hEvent = m_hevtOverlapped; } bool CVsTstPipe::WaitForConnection() { SetupOverlapped(); if (ConnectNamedPipe(m_hPipe, &m_overlap)) return true; if (GetLastError() == ERROR_IO_PENDING) { while(TRUE) { if (!m_pHandler->m_bReadEnabled) { CancelIo(m_hPipe); return false; } DWORD dwErr = WaitForSingleObject(m_hevtOverlapped, 1000); if (dwErr == WAIT_OBJECT_0) break; if (dwErr == WAIT_FAILED) { Sleep(1000); CancelIo(m_hPipe); return false; } } } return true; } // do the work of reading messages VSTST_READER_STATUS CVsTstPipe::ReadMessages(bool bConnect) { if (bConnect) { if (!WaitForConnection()) return VSTST_RS_NOTCONNECTED; // launch a new reader thread to wait for the next connection m_pHandler->LaunchReader(); } // while we are doing reads while(m_pHandler->m_bReadEnabled) { DWORD cbRead; // setup overlapped structure SetupOverlapped(); if (!ReadFile ( m_hPipe, m_rgbMsg, m_cbMsg, &cbRead, &m_overlap )) { DWORD dwErr = GetLastError(); if (dwErr == ERROR_IO_PENDING) { while(TRUE) { if (!m_pHandler->m_bReadEnabled) { CancelIo(m_hPipe); return VSTST_RS_READDISABLED; } DWORD dwErr = WaitForSingleObject(m_hevtOverlapped, 1000); if (dwErr == WAIT_OBJECT_0) break; if (dwErr == WAIT_FAILED) { Sleep(1000); CancelIo(m_hPipe); continue; } } if (!GetOverlappedResult(m_hPipe, &m_overlap, &cbRead, FALSE)) { CancelIo(m_hPipe); continue; } } else { // unexpected error reading from pipe DWORD dwErr = GetLastError(); if (dwErr == ERROR_BROKEN_PIPE) { // terminate thread as we are no longer reading from // the pipe DisconnectNamedPipe(m_hPipe); return VSTST_RS_DISCONNECTED; } else ReadPipeError(dwErr); return VSTST_RS_ERROR; } } VSTST_MSG_HDR *phdr = (VSTST_MSG_HDR *) m_rgbMsg; if (phdr->cbMsg != cbRead || phdr->type == VSTST_MT_UNDEFINED || phdr->type >= VSTST_MT_MAXMSGTYPE) LogInvalidMessage(phdr); else if (g_msgTypes[phdr->type].priority == VSTST_MP_IMMEDIATE) { if (!m_pHandler->ProcessMsgImmediate(phdr)) LogInvalidMessage(phdr); } else { if (!m_pHandler->QueueMsg(phdr)) LogInvalidMessage(phdr); } } return VSTST_RS_READDISABLED; } DWORD CVsTstPipe::StartReaderThread(VOID *pv) { CVsTstPipe *pipe = (CVsTstPipe *) pv; bool bConnected = false; try { while(!pipe->m_pHandler->m_bTerminateReader) { if (WaitForSingleObject(pipe->m_pHandler->m_hevtReader, INFINITE) == WAIT_FAILED) break; VSTST_READER_STATUS status = pipe->ReadMessages(!bConnected); if (status == VSTST_RS_DISCONNECTED) { bConnected = false; break; } else if (status != VSTST_RS_NOTCONNECTED) bConnected = true; } } catch(...) { LogUnexpectedFailure(L"Read thread unexpectedly terminated"); } delete pipe; return 0; }