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;
}