Search code examples
c++multithreadingvisual-studio-2008serial-communication

Serial Port Receive Thread Not Behaving as Expected - C++


My application uses a separate thread for handling received serial data asynchronously. The PC gets into the receive-handler as expected, but from there things go weird.

This is my thread function:

// Create event for OVERLAPPED structure.
s_ov.hEvent = ::CreateEvent(
    NULL,                           // No security
    TRUE,                           // Create a manual-reset event object
    FALSE,                          // Initial state is non-signaled
    NULL                            // No name specified
    );

// Load event handles.
pHandles[0] = s_hSerialPortRxThreadExitEvent;

while ( bContinue )
{
    if ( !::WaitCommEvent( s_hSerialPort, &dwEventMask, &s_ov ) )
    {
        if ( ::GetLastError() != ERROR_IO_PENDING )
        {
            TRACE(_T("SerialPortRxThreadFn : Call to WaitCommEvent failed.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
            return ::GetLastError();
        }
    }

    pHandles[1] = s_ov.hEvent;

    dwObjectWaitState = ::WaitForMultipleObjects( 2, pHandles, FALSE, INFINITE );

    switch ( dwObjectWaitState )
    {
    case WAIT_ABANDONED:
        TRACE(_T("SerialPortRxThreadFn : Owner thread terminated prematurely.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ERROR_ARENA_TRASHED, __WFILE__, __LINE__);
        return ERROR_ARENA_TRASHED;
        break;

    case WAIT_TIMEOUT:
        TRACE(_T("SerialPortRxThreadFn : The timeout is set to INFINITE; there should be no timeout.  State is nonsignaled.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), WAIT_TIMEOUT, __WFILE__, __LINE__);
        return WAIT_TIMEOUT;
        break;

    case WAIT_FAILED:
        TRACE(_T("SerialPortRxThreadFn : Call to WaitCommEvent failed.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
        return ::GetLastError();
        break;

    case WAIT_OBJECT_0:             // thread exit event signalled
        bContinue = FALSE;

        if ( !::ResetEvent( pHandles[0] ) )
        {
            TRACE(_T("SerialPortRxThreadFn  : Failed to reset the serial port thread exit event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
            return ::GetLastError();
        }
        break;

    case WAIT_OBJECT_0 + 1:         // OVERLAPPED structure event signalled
        // Read data from serial port.
        if ( !::ReadFile( s_hSerialPort, pBuf, RX_BUF_SIZE, &dwWritten, &s_ov ) ) // <- Set breakpoint here
        {
            TRACE(_T("SerialPortRxThreadFn : Call to ReadFile filed.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
            return ::GetLastError();
        }

        // Discontinue thread operation if there are no more bytes in the serial port receive buffer.
        if ( dwWritten == 0 ) // <- Or, set breakpoint here
        {
            bContinue = FALSE;
        }
        // Copy the received bytes to the thread-safe buffer.
        else if ( !s_pobjRxRingBuffer->Add( pBuf, dwWritten, TRUE ) )
        {
            TRACE(_T("SerialPortRxThreadFn : Failed to add bytes to ring buffer.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ERROR_INSUFFICIENT_BUFFER, __WFILE__, __LINE__);
            return ERROR_INSUFFICIENT_BUFFER;
        }
        else if ( s_SpCallbackFn != NULL )
        {
            // Notify application of received data.
            if ( (dwRetVal = s_SpCallbackFn( s_pobjRxRingBuffer->ItemsInBuffer() )) != ERROR_SUCCESS )
            {
                TRACE(_T("SerialPortRxThreadFn : Serial port callback function failed.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), dwRetVal, __WFILE__, __LINE__);
                return dwRetVal;
            }
        }

        if ( !::ResetEvent( pHandles[1] ) )
        {
            TRACE(_T("SerialPortRxThreadFn : Failed to reset the OVERLAPPED structure event.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
            return ::GetLastError();
        }
        break;

    default:
        // Do nothing.
        break;
    }
}

::CloseHandle( s_ov.hEvent );

return ERROR_SUCCESS;

If I set my breakpoint on the line calling ReadFile everything works as I expect, and the PC gets into the callback function. However, if I set my breakpoint at the next line, where dwWritten is evaluated for zero, it is zero, the expression evaluates as TRUE, and the loop exits; the PC never gets to the callback. What am I doing wrong? Thanks.


Solution

  • I am no expert on the Win32 API, but it sure sounds like a timing issue (which is a common cause of heisenbugs.) Let's say by the time you get to ReadFile, there is no data to read. Breaking into the debugger might give it enough of a pause for the data to arrive, so when you resume/step over ReadFile, it succeeds.

    There are lots of things other than the arrival of data that could trigger the event. You may want to check your dwEventMask to see if my hypothesis is true.