Posted to tcl by patthoyts at Thu Aug 14 23:08:59 GMT 2008view pretty
// ThreadCrash.cpp - // // Written in Munich airport to test thread safety of CComBSTR assignment. // The CComBSTR in the CThing class is not being used safely -- equivalent to // the usage we see in the CWiREQueueCom error handling. What we expect to see // here is a context switch in the middle of a CComBSTR::operator= which leaves // the object in partial state - the BSTR is not fully assigned. Then the next // thread tries to free the partially assigned BSTR and we get an exception // where windows claims an invalid address has been passed to SysFreeAlloc(). // The solution is to be thread safe - either by keeping the string local or // by using a lock around member variable access. // // On a single core cpu this program will work correctly. The problem shows // up when you have either hyperthreading or multiple-cores. // // Compile using: // cl -nologo -W4 -MD -O2 -Gs ThreadCrash.cpp -link -debug -subsystem:console // // $Id$ #define STRICT #define WIN32_LEAN_AND_MEAN #define UNICODE #if defined(UNICODE) && !defined(_UNICODE) # define _UNICODE #endif #include <atlbase.h> #include <process.h> #include <tchar.h> class CThing { public: CComBSTR m_String; }; class CThread { public: CThread() : m_ID(0xffffffff), m_pThing(0) {} DWORD m_ID; CThing *m_pThing; }; DWORD WINAPI BeginThread(LPVOID pClientData) { CThread *pThread = (CThread *)pClientData; _tprintf(_T("Thread 0x%04x started\n"), pThread->m_ID); WCHAR wsz[18]; _snwprintf(wsz, 17, L"Thread 0x%04x", pThread->m_ID); // This number should be sufficient that we get some context switches // before we have finished the job. We cannot use a Sleep() or the // context switch will occur when we call Sleep(). const int max = 1000000; for (int n = 0; n < max; n++) { pThread->m_pThing->m_String = wsz; } _tprintf(_T("Thread 0x%04x completed\n"), pThread->m_ID); return 0; } static void printerror(DWORD dwError) { LPTSTR sMsg; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&sMsg, 0, NULL); _tprintf(sMsg); LocalFree(sMsg); } int _tmain(void) { CThing * pThing = new CThing; CThread * pThread[10]; HANDLE hThread[10]; unsigned long n, i; // Start all the threads in a suspended state. Each thread gets it's own // object but all objects point to the same CComBSTR via the same CThing // instance. for (n = 0; n < sizeof(hThread)/sizeof(hThread[0]); ++n) { pThread[n] = new CThread(); pThread[n]->m_pThing = pThing; hThread[n] = CreateThread(0, 0, BeginThread, (LPVOID)pThread[n], CREATE_SUSPENDED, &pThread[n]->m_ID); } // Start all the threads at the same time. for (i = 0; i < n; ++i) ResumeThread(hThread[i]); // Wait for all threads to complete. while (1) { DWORD dwWait = WaitForMultipleObjects(n, hThread, TRUE, 1000); if (dwWait == WAIT_TIMEOUT) { _tprintf(_T("tick\n")); } else if (dwWait >= WAIT_OBJECT_0 && dwWait < (WAIT_OBJECT_0 + n)) { break; } else { printerror(GetLastError()); } } // Clean up politely. for (i = 0; i < n; i++) CloseHandle(hThread[i]); return 0; }