void DbgTransportSession::TransportWorker()

in src/coreclr/debug/shared/dbgtransportsession.cpp [1259:2168]


void DbgTransportSession::TransportWorker()
{
    _ASSERTE(m_eState == SS_Opening_NC);

    // Loop until shutdown. Each loop iteration involves forming a connection (or waiting for one to form)
    // followed by processing incoming messages on that connection until there's a failure (either here of
    // from a send on another thread) or the session shuts down. The connection is then closed and discarded
    // and we either go round the loop again (to recover our previous session state) or exit the method as
    // part of shutdown.
  ResetConnection:
    while (m_eState != SS_Closed)
    {
        _ASSERTE(m_eState == SS_Opening_NC || m_eState == SS_Resync_NC || m_eState == SS_Closed);

        DbgTransportLog(LC_Proxy, "Forming new connection");

#ifdef RIGHT_SIDE_COMPILE
        // The session is definitely not open at this point.
        ResetEvent(m_hSessionOpenEvent);

        // On the right side we initiate the connection via Connect(). A failure is dealt with by waiting a
        // little while and retrying (the LS may take a little while to set up). If there's nobody listening
        // the debugger will eventually get bored waiting for us and shutdown the session, which will
        // terminate this loop.
        ConnStatus eStatus;
        if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Connect))
            eStatus = SCS_NetworkFailure;
        else
        {
            if (m_pipe.Connect(m_pd))
            {
                eStatus = SCS_Success;
            }
            else
            {
                //not really sure that this is the real failure
                //TODO: we probably need to analyse GetErrorCode() here
                eStatus = SCS_NoListener;
            }
        }

        if (eStatus != SCS_Success)
        {
            DbgTransportLog(LC_Proxy, "AllocateConnection() failed with %u\n", eStatus);
            DBG_TRANSPORT_INC_STAT(MiscErrors);
            _ASSERTE(m_pipe.GetState() != TwoWayPipe::ClientConnected);
            Sleep(1000);
            continue;
        }
#else // RIGHT_SIDE_COMPILE
        ConnStatus eStatus;
        if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Accept))
            eStatus = SCS_NetworkFailure;
        else
        {
            ProcessDescriptor pd = ProcessDescriptor::FromCurrentProcess();
            if ((m_pipe.GetState() == TwoWayPipe::Created || m_pipe.CreateServer(pd)) &&
                 m_pipe.WaitForConnection())
            {
                eStatus = SCS_Success;
            }
            else
            {
                //not really sure that this is the real failure
                //TODO: we probably need to analyse GetErrorCode() here
                eStatus = SCS_NoListener;
            }
        }

        if (eStatus != SCS_Success)
        {
            DbgTransportLog(LC_Proxy, "Accept() failed with %u\n", eStatus);
            DBG_TRANSPORT_INC_STAT(MiscErrors);
            _ASSERTE(m_pipe.GetState() != TwoWayPipe::ServerConnected);
            Sleep(1000);
            continue;
        }

        // Note that when resynching a session we may let in a connection from a different debugger. That's
        // OK, we'll reject its SessionRequest message in due course and drop the connection.
#endif // RIGHT_SIDE_COMPILE

        DBG_TRANSPORT_INC_STAT(Connections);

        // We now have a connection. Transition to the next state (either SS_Opening or SS_Resync). The
        // primary purpose of this state transition is to let other threads know that this thread might now be
        // blocked on a Receive() on the newly formed connection (important if they want to transition the state
        // to SS_Closed).
        {
            TransportLockHolder sLockHolder(&m_sStateLock);

            if (m_eState == SS_Closed)
                break;
            else if (m_eState == SS_Opening_NC)
                m_eState = SS_Opening;
            else if (m_eState == SS_Resync_NC)
                m_eState = SS_Resync;
            else
                _ASSERTE(!"Bad session state");
        } // Leave m_sStateLock


        // Now we have a connection in place. Start reading messages and processing them. Which messages are
        // valid depends on whether we're in SS_Opening or SS_Resync (the state can change at any time
        // asynchronously to us to either SS_Closed or SS_Resync_NC but we're guaranteed the connection stays
        // valid (though not necessarily useful) until we notice this state change and Destroy() it ourself).
        // We check the state after each network operation.

        // During the SS_Opening and SS_Resync states we're guarantee to be the only thread posting sends, so
        // we can break the rules and use SendBlock without acquiring the state lock. (We use SendBlock a lot
        // during these phases because we're using simple Session* messages which don't require the extra
        // processing SendMessage gives us such as encryption or placement on the send queue).

        MessageHeader   sSendHeader;
        MessageHeader   sReceiveHeader;

        memset(&sSendHeader, 0, sizeof(MessageHeader));

        if (m_eState == SS_Opening)
        {
#ifdef RIGHT_SIDE_COMPILE
            // The right side actually starts things off by sending a SessionRequest message.

            SessionRequestData sDataBlock;

            sSendHeader.m_eType = MT_SessionRequest;
            sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
            sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = kCurrentMinorVersion;

            // The start of the data block always contains a session ID. This is a GUID randomly generated at
            // Init() time.
            sSendHeader.m_cbDataBlock = sizeof(SessionRequestData);
            memcpy(&sDataBlock.m_sSessionID, &m_sSessionID, sizeof(m_sSessionID));

            // Send the header block followed by the data block. For failures during SS_Opening we just close
            // the connection and retry from the beginning (the failing send will already have caused a
            // transition into SS_Opening_NC. No need to use the same resend logic that SS_Resync does, since
            // no user messages have been sent and we can simply recreate the SessionRequest.
            DbgTransportLog(LC_Session, "Sending 'SessionRequest'");
            DBG_TRANSPORT_INC_STAT(SentSessionRequest);
            if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)) ||
                !SendBlock((PBYTE)&sDataBlock, sSendHeader.m_cbDataBlock))
                HANDLE_TRANSIENT_ERROR();

            // Wait for a reply.
            if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
                HANDLE_TRANSIENT_ERROR();

            DbgTransportLogMessageReceived(&sReceiveHeader);

            // This should be either a SessionAccept or SessionReject. Any other message type will be treated
            // as a SessionReject (i.e. an unrecoverable failure that will leave the session in SS_Closed
            // permanently).
            if (sReceiveHeader.m_eType != MT_SessionAccept)
            {
                _ASSERTE(!"Unexpected response to SessionRequest");
                HANDLE_CRITICAL_ERROR();
            }

            // Validate the SessionAccept.
            if (sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion != kCurrentMajorVersion ||
                sReceiveHeader.m_cbDataBlock != (DWORD)0)
            {
                _ASSERTE(!"Malformed SessionAccept received");
                HANDLE_CRITICAL_ERROR();
            }

            // The LS might have negotiated the minor protocol version down.
            m_dwMinorVersion = sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion;
#else // RIGHT_SIDE_COMPILE

            // On the left side we wait for a SessionRequest first.
            if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
                HANDLE_TRANSIENT_ERROR();

            DbgTransportLogMessageReceived(&sReceiveHeader);

            if (sReceiveHeader.m_eType != MT_SessionRequest)
            {
                _ASSERTE(!"Unexpected message type");
                HANDLE_CRITICAL_ERROR();
            }

            // Validate the SessionRequest.
            if (sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion != kCurrentMajorVersion ||
                sReceiveHeader.m_cbDataBlock != (DWORD)sizeof(SessionRequestData))
            {
                // Send a SessionReject message with the reason for rejection.
                sSendHeader.m_eType = MT_SessionReject;
                sSendHeader.TypeSpecificData.SessionReject.m_eReason = RR_IncompatibleVersion;
                sSendHeader.TypeSpecificData.SessionReject.m_dwMajorVersion = kCurrentMajorVersion;
                sSendHeader.TypeSpecificData.SessionReject.m_dwMinorVersion = kCurrentMinorVersion;

                DbgTransportLog(LC_Session, "Sending 'SessionReject(RR_IncompatibleVersion)'");
                DBG_TRANSPORT_INC_STAT(SentSessionReject);

                SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader));

                // Go back into the opening state rather than closed because we want to give the RS a chance
                // to correct the problem and try again.
                HANDLE_TRANSIENT_ERROR();
            }

            // Read the data block.
            SessionRequestData sDataBlock;
            if (!ReceiveBlock((PBYTE)&sDataBlock, sizeof(SessionRequestData)))
                HANDLE_TRANSIENT_ERROR();

            // If the RS only understands a lower minor protocol version than us then remember that fact.
            if (sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion < m_dwMinorVersion)
                m_dwMinorVersion = sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion;

            // Send a SessionAccept message back.
            sSendHeader.m_eType = MT_SessionAccept;
            sSendHeader.m_cbDataBlock = 0;
            sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
            sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = m_dwMinorVersion;

            DbgTransportLog(LC_Session, "Sending 'SessionAccept'");
            DBG_TRANSPORT_INC_STAT(SentSessionAccept);

            if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)))
                HANDLE_TRANSIENT_ERROR();
#endif // RIGHT_SIDE_COMPILE

            // Everything pans out, we have a session formed. But we must send messages that queued up
            // before transitioning the state to open (otherwise a racing send could sneak in ahead).

            // Must access the send queue under the state lock.
            {
                TransportLockHolder sLockHolder(&m_sStateLock);
                Message *pMsg = m_pSendQueueFirst;
                while (pMsg)
                {
                    if (SendBlock((PBYTE)&pMsg->m_sHeader, sizeof(MessageHeader)) && pMsg->m_pbDataBlock)
                        SendBlock(pMsg->m_pbDataBlock, pMsg->m_cbDataBlock);
                    pMsg = pMsg->m_pNext;
                }

                // Check none of the sends failed.
                if (m_eState != SS_Opening)
                {
                    m_pipe.Disconnect();
                    continue;
                }
            } // Leave m_sStateLock

            // Finally we can transition to SS_Open.
            {
                TransportLockHolder sLockHolder(&m_sStateLock);
                if (m_eState == SS_Closed)
                    break;
                else if (m_eState == SS_Opening)
                    m_eState = SS_Open;
                else
                    _ASSERTE(!"Bad session state");
            } // Leave m_sStateLock

#ifdef RIGHT_SIDE_COMPILE
            // Signal any WaitForSessionToOpen() waiters that we've gotten to SS_Open.
            SetEvent(m_hSessionOpenEvent);
#endif // RIGHT_SIDE_COMPILE

            // We're ready to begin receiving normal incoming messages now.
        }
        else
        {
            // The SS_Resync case. Send a message indicating the last message we saw from the other side and
            // wait for a similar message to arrive for us.

            sSendHeader.m_eType = MT_SessionResync;
            sSendHeader.m_dwLastSeenId = m_dwLastMessageIdSeen;

            DbgTransportLog(LC_Session, "Sending 'SessionResync'");
            DBG_TRANSPORT_INC_STAT(SentSessionResync);

            if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)))
                HANDLE_TRANSIENT_ERROR();

            if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
                HANDLE_TRANSIENT_ERROR();

#ifndef RIGHT_SIDE_COMPILE
            if (sReceiveHeader.m_eType == MT_SessionRequest)
            {
                DbgTransportLogMessageReceived(&sReceiveHeader);

                // This SessionRequest could be from a different debugger. In this case we should send a
                // SessionReject to let them know we're not available and close the connection so we can
                // re-listen for the original debugger.
                // Or it could be the original debugger re-sending the SessionRequest because the connection
                // died as we sent the SessionAccept.
                // We distinguish the two cases by looking at the session ID in the request.
                bool fRequestResend = false;

                // Only read the data block if it matches our expectations of its size.
                if (sReceiveHeader.m_cbDataBlock == (DWORD)sizeof(SessionRequestData))
                {
                    SessionRequestData sDataBlock;
                    if (!ReceiveBlock((PBYTE)&sDataBlock, sizeof(SessionRequestData)))
                        HANDLE_TRANSIENT_ERROR();

                    // Check the session ID for a match.
                    if (memcmp(&sDataBlock.m_sSessionID, &m_sSessionID, sizeof(m_sSessionID)) == 0)
                        // OK, everything checks out and this is a valid re-send of a SessionRequest.
                        fRequestResend = true;
                }

                if (fRequestResend)
                {
                    // The RS never got our SessionAccept. We must resend it.
                    memset(&sSendHeader, 0, sizeof(MessageHeader));
                    sSendHeader.m_eType = MT_SessionAccept;
                    sSendHeader.m_cbDataBlock = 0;
                    sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
                    sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = m_dwMinorVersion;

                    DbgTransportLog(LC_Session, "Sending 'SessionAccept'");
                    DBG_TRANSPORT_INC_STAT(SentSessionAccept);

                    if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)))
                        HANDLE_TRANSIENT_ERROR();

                    // Now simply reset the connection. The RS should get the SessionAccept and transition to
                    // SS_Open then detect the connection loss and transition to SS_Resync_NC, which will
                    // finally sync the two sides.
                    HANDLE_TRANSIENT_ERROR();
                }
                else
                {
                    // This is the case where we must reject the request.
                    memset(&sSendHeader, 0, sizeof(MessageHeader));
                    sSendHeader.m_eType = MT_SessionReject;
                    sSendHeader.TypeSpecificData.SessionReject.m_eReason = RR_AlreadyAttached;
                    sSendHeader.TypeSpecificData.SessionReject.m_dwMajorVersion = kCurrentMajorVersion;
                    sSendHeader.TypeSpecificData.SessionReject.m_dwMinorVersion = kCurrentMinorVersion;

                    DbgTransportLog(LC_Session, "Sending 'SessionReject(RR_AlreadyAttached)'");
                    DBG_TRANSPORT_INC_STAT(SentSessionReject);

                    SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader));

                    HANDLE_TRANSIENT_ERROR();
                }
            }
#endif // !RIGHT_SIDE_COMPILE

            DbgTransportLogMessageReceived(&sReceiveHeader);

            // Handle all other invalid message types by shutting down (it may be an attempt to subvert the
            // protocol).
            if (sReceiveHeader.m_eType != MT_SessionResync)
            {
                _ASSERTE(!"Unexpected message type during SS_Resync");
                HANDLE_CRITICAL_ERROR();
            }

            // We've got our resync message. Go through the send queue and resend any messages that haven't
            // been processed by the other side. Those that have been processed can be discarded (unless
            // they're waiting for another form of higher level acknowledgement, such as a reply message).

            // Discard unneeded messages first.
            FlushSendQueue(sReceiveHeader.m_dwLastSeenId);

            // Must access the send queue under the state lock.
            {
                TransportLockHolder sLockHolder(&m_sStateLock);

                Message *pMsg = m_pSendQueueFirst;
                while (pMsg)
                {
                    if (pMsg->m_sHeader.m_dwId > sReceiveHeader.m_dwLastSeenId)
                    {
                        // The other side never saw this message, re-send it.
                        DBG_TRANSPORT_INC_STAT(Resends);
                        if (SendBlock((PBYTE)&pMsg->m_sHeader, sizeof(MessageHeader)) && pMsg->m_pbDataBlock)
                            SendBlock(pMsg->m_pbDataBlock, pMsg->m_cbDataBlock);
                    }
                    pMsg = pMsg->m_pNext;
                }

                // Finished processing queued sends. We can transition to the SS_Open state now as long as there
                // wasn't a send failure or an asynchronous Shutdown().
                if (m_eState == SS_Resync)
                    m_eState = SS_Open;
                else if (m_eState == SS_Closed)
                    break;
                else if (m_eState == SS_Resync_NC)
                {
                    m_pipe.Disconnect();
                    continue;
                }
                else
                    _ASSERTE(!"Bad session state");
            } // Leave m_sStateLock
        }

        // Once we get here we should be in SS_Open (can't assert this because Shutdown() can throw the state
        // into SS_Closed and we've just released SendMessage() calls on other threads that can transition us
        // into SS_Resync).

        // We now loop receiving messages and processing them until the state changes.
        while (m_eState == SS_Open)
        {
#ifndef RIGHT_SIDE_COMPILE
            // temporary data block used in DCB messages
            DebuggerIPCControlBlockTransport dcbt;

            // temporary virtual stack unwind context buffer
            CONTEXT frameContext;
#endif

            // Read a message header block.
            if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
                HANDLE_TRANSIENT_ERROR();

            // Since we care about security here, perform some additional validation checks that make it
            // harder for a malicious sender to attack with random message data.
            if (sReceiveHeader.m_eType > MT_GetAppDomainCB ||
                (sReceiveHeader.m_dwId <= m_dwLastMessageIdSeen &&
                 sReceiveHeader.m_dwId != (DWORD)0) ||
                (sReceiveHeader.m_dwReplyId >= m_dwNextMessageId &&
                 sReceiveHeader.m_dwReplyId != (DWORD)0) ||
                (sReceiveHeader.m_dwLastSeenId >= m_dwNextMessageId &&
                 sReceiveHeader.m_dwLastSeenId != (DWORD)0))
            {
                _ASSERTE(!"Incoming message header looks bogus");
                HANDLE_CRITICAL_ERROR();
            }

            DbgTransportLogMessageReceived(&sReceiveHeader);

            // Flush any entries in our send queue for messages that the other side has just confirmed
            // processed with this message.
            FlushSendQueue(sReceiveHeader.m_dwLastSeenId);

#ifndef RIGHT_SIDE_COMPILE
            // State variables to track whether this message needs a reply and if so whether it consists of a
            // header only or a header and an optional data block.
            bool    fReplyRequired = false;
            PBYTE   pbOptReplyData = NULL;
            DWORD   cbOptReplyData = 0;
            HRESULT hr             = E_FAIL;

            // if you change the lifetime of resultBuffer, make sure you change pbOptReplyData to match.
            // In some cases pbOptReplyData will point at the memory held alive in resultBuffer
            WriteBuffer resultBuffer;
            ReadBuffer  receiveBuffer;

#endif // RIGHT_SIDE_COMPILE

            // Dispatch based on message type.
            //
            // **** IMPORTANT NOTE ****
            //
            // We must be very careful wrt to updating m_dwLastMessageIdSeen here. If we update it too soon
            // (we haven't finished receiving the entire message, for instance) then the other side won't
            // re-send the message on failure and we'll lose it. If we update it too late we might have
            // reported the message to our caller or produced any other side-effect we can't take back such as
            // sending a reply and then hit an error and reset the connection before we had a chance to record
            // the message as seen. In this case the other side will re-send the original message and we'll
            // repeat our actions, which is also very bad.
            //
            // So we must be very disciplined here.
            //
            // First we must read the message in its entirety (i.e. receive the data block if there is one)
            // without causing any side-effects. This ensures that any failure at this point will be handled
            // correctly (by the other side re-sending us the same message).
            //
            // Then we process the message. At this point we are committed. The processing must always
            // succeed, or have no side-effect (that we care about) or we must have an additional scheme to
            // handle resynchronization in the event of failure. This ensures that we don't have the tricky
            // situation where we can't cope with a re-send of the message (because we've started processing
            // it) but can't report a failure to the other side (because we don't know how).
            //
            // Finally we must ensure that there is no error path between the completion of processing and
            // updating the m_dwLastMessageIdSeen field. This ensures we don't accidently get re-sent a
            // message we've processed completely (it's really just a sub-case of the rule above, but it's
            // worth pointing out explicitly since it can be a subtle problem).
            //
            // Request messages (such as MT_GetDCB) are an interesting case in point here. They all require a
            // reply and we can fail on the reply because we run out of system resources. This breaks the
            // second rule above (we fail halfway through processing). We should really preallocate enough
            // resources to send the reply before we begin processing of it but for now we don't since (a) the
            // SendMessage system isn't currently set up to make this easy and (b) we happen to know that all
            // the request types are effectively idempotent (even ReadMemory and WriteMemory since the RS is
            // holding the LS still while it does these). So instead we must carefully distinguish the case
            // where SendMessage fails without possibility of message transmission (e.g. out of memory) and
            // those where it fails for a transient network failure (where it will re-send the reply on
            // resync). This is easy enough to do since SendMessage returns a failure hresult for the first
            // case and success (and a state transition) for the second. In the first case we don't update
            // m_dwLastMessageIdSeen and instead wait for the request to be resent. In the second we make the
            // update because we know the reply will get through eventually.
            //
            // **** IMPORTANT NOTE ****
            switch (sReceiveHeader.m_eType)
            {
            case MT_SessionRequest:
            case MT_SessionAccept:
            case MT_SessionReject:
            case MT_SessionResync:
                // Illegal messages at this time, fail the transport entirely.
                m_eState = SS_Closed;
                break;

            case MT_SessionClose:
                // Close is legal on the LS and transitions to the SS_Opening_NC state. It's illegal on the RS
                // and should shutdown the transport.
#ifdef RIGHT_SIDE_COMPILE
                m_eState = SS_Closed;
                break;
#else // RIGHT_SIDE_COMPILE
                // We need to do some state cleanup here, since when we reform a connection (if ever, it will
                // be with a new session).
                {
                    TransportLockHolder sLockHolder(&m_sStateLock);

                    // Check we're still in a good state before a clean restart.
                    if (m_eState != SS_Open)
                    {
                        m_eState = SS_Closed;
                        break;
                    }

                    m_pipe.Disconnect();

                    // We could add code to drain the send queue here (like we have for SS_Closed at the end of
                    // this method) but I'm pretty sure we can only get a graceful session close with no
                    // outstanding sends. So just assert the queue is empty instead. If the assert fires and it's
                    // not due to an issue we can add the logic here).
                    _ASSERTE(m_pSendQueueFirst == NULL);
                    _ASSERTE(m_pSendQueueLast == NULL);

                    // This will reset all session specific state and transition us to SS_Opening_NC.
                    InitSessionState();
                } // Leave m_sStateLock

                goto ResetConnection;
#endif // RIGHT_SIDE_COMPILE

            case MT_Event:
            {
                // Incoming debugger event.

                if (sReceiveHeader.m_cbDataBlock > CorDBIPC_BUFFER_SIZE)
                {
                    _ASSERTE(!"Oversized Event");
                    HANDLE_CRITICAL_ERROR();
                }

                // See if our array of buffered events has filled up. If so we'll need to re-allocate the
                // array to expand it.
                if (m_cValidEventBuffers == m_cEventBuffers)
                {
                    // Allocate a larger array.
                    DWORD cNewEntries = m_cEventBuffers + 4;
                    DbgEventBufferEntry * pNewBuffers = (DbgEventBufferEntry *)new (nothrow) BYTE[cNewEntries * sizeof(DbgEventBufferEntry)];
                    if (pNewBuffers == NULL)
                        HANDLE_TRANSIENT_ERROR();

                    // We must take the lock to swap the new array in. Although this thread is the only one
                    // that can expand the array, a client thread may be in GetNextEvent() reading from the
                    // old version.
                    {
                        TransportLockHolder sLockHolder(&m_sStateLock);

                        // When we copy old array contents over we place the head of the list at the start of
                        // the new array for simplicity. If the head happened to be at the start of the old
                        // array anyway, this is even simpler.
                        if (m_idxEventBufferHead == 0)
                            memcpy(pNewBuffers, m_pEventBuffers, m_cEventBuffers * sizeof(DbgEventBufferEntry));
                        else
                        {
                            // Otherwise we need to perform the copy in two segments: first we copy the head
                            // of the list (starts at a non-zero index and runs to the end of the old array)
                            // into the start of the new array.
                            DWORD cHeadEntries = m_cEventBuffers - m_idxEventBufferHead;

                            memcpy(pNewBuffers,
                                   &m_pEventBuffers[m_idxEventBufferHead],
                                   cHeadEntries * sizeof(DbgEventBufferEntry));

                            // Then we copy the remaining portion from the beginning of the old array upto to
                            // the index of the head.
                            memcpy(&pNewBuffers[cHeadEntries],
                                   m_pEventBuffers,
                                   m_idxEventBufferHead * sizeof(DbgEventBufferEntry));
                        }

                        // Delete the old array.
                        delete [] m_pEventBuffers;

                        // Swap the new array in.
                        m_pEventBuffers = pNewBuffers;
                        m_cEventBuffers = cNewEntries;

                        // The new array now has the head at index zero and the tail at the start of the
                        // new entries.
                        m_idxEventBufferHead = 0;
                        m_idxEventBufferTail = m_cValidEventBuffers;
                    }
                }

                // We have at least one free buffer at this point (no threading issues, the only thread that
                // can add entries is this one).

                // Receive event data into the tail buffer (we want to do this without holding the state lock
                // and can do so safely since this is the only thread that can receive data and clients can do
                // nothing that impacts the location of the tail of the buffer list).
                if (!ReceiveBlock((PBYTE)&m_pEventBuffers[m_idxEventBufferTail].m_event, sReceiveHeader.m_cbDataBlock))
                    HANDLE_TRANSIENT_ERROR();

                {
                    m_pEventBuffers[m_idxEventBufferTail].m_type = sReceiveHeader.TypeSpecificData.Event.m_eIPCEventType;

                    // We must take the lock to update the count of valid entries though, since clients can
                    // touch this field as well.
                    TransportLockHolder sLockHolder(&m_sStateLock);

                    m_cValidEventBuffers++;
                    DWORD idxCurrentEvent = m_idxEventBufferTail;

                    // Update tail of the list (strictly speaking this needn't be done under the lock, but the
                    // code in GetNextEvent() does read it for an assert.
                    m_idxEventBufferTail = (m_idxEventBufferTail + 1) % m_cEventBuffers;

                    // If we just added the first valid event then wake up the client so they can call
                    // GetNextEvent().
                    if (m_cValidEventBuffers == 1)
                        SetEvent(m_rghEventReadyEvent[m_pEventBuffers[idxCurrentEvent].m_type]);
                }
            }
            break;

            case MT_ReadMemory:
#ifdef RIGHT_SIDE_COMPILE
                if (!ProcessReply(&sReceiveHeader))
                    HANDLE_TRANSIENT_ERROR();
#else // RIGHT_SIDE_COMPILE
                // The RS wants to read our memory. First check the range requested is both committed and
                // readable. If that succeeds we simply set the optional reply block to match the request region
                // (i.e. we send the memory directly).
                fReplyRequired = true;

                hr = CheckBufferAccess(sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
                                       sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer,
                                       false);
                sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult = hr;
                if (SUCCEEDED(hr))
                {
                    pbOptReplyData = sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer;
                    cbOptReplyData = sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer;
                }
#endif // RIGHT_SIDE_COMPILE
                break;

            case MT_WriteMemory:
#ifdef RIGHT_SIDE_COMPILE
                if (!ProcessReply(&sReceiveHeader))
                    HANDLE_TRANSIENT_ERROR();
#else // RIGHT_SIDE_COMPILE
                // The RS wants to write our memory.
                if (sReceiveHeader.m_cbDataBlock != sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer)
                {
                    _ASSERTE(!"Inconsistent WriteMemory request");
                    HANDLE_CRITICAL_ERROR();
                }

                fReplyRequired = true;

                // Check the range requested is both committed and writeable. If that succeeds we simply read
                // the next incoming block into the destination buffer.
                hr = CheckBufferAccess(sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
                                       sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer,
                                       true);
                if (SUCCEEDED(hr))
                {
                    if (!ReceiveBlock(sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
                                      sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer))
                        HANDLE_TRANSIENT_ERROR();
                }
                else
                {
                    sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult = hr;

                    // We might be failing the write attempt but we still need to read the update data to
                    // drain it from the connection or we'll become unsynchronized (i.e. we'll treat the start
                    // of the write data as the next message header). So read and discard the data into a
                    // dummy buffer.
                    BYTE    rgDummy[256];
                    DWORD   cbBytesToRead = sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer;
                    while (cbBytesToRead)
                    {
                        DWORD cbTransfer = min(cbBytesToRead, (DWORD)sizeof(rgDummy));
                        if (!ReceiveBlock(rgDummy, cbTransfer))
                            HANDLE_TRANSIENT_ERROR();
                        cbBytesToRead -= cbTransfer;
                    }
                }
#endif // RIGHT_SIDE_COMPILE
                break;

            case MT_VirtualUnwind:
#ifdef RIGHT_SIDE_COMPILE
                if (!ProcessReply(&sReceiveHeader))
                    HANDLE_TRANSIENT_ERROR();
#else // RIGHT_SIDE_COMPILE
                if (sReceiveHeader.m_cbDataBlock != (DWORD)sizeof(frameContext))
                {
                    _ASSERTE(!"Inconsistent VirtualUnwind request");
                    HANDLE_CRITICAL_ERROR();
                }

                if (!ReceiveBlock((PBYTE)&frameContext, sizeof(frameContext)))
                {
                    HANDLE_TRANSIENT_ERROR();
                }

                if (!PAL_VirtualUnwind(&frameContext, NULL))
                {
                    HANDLE_TRANSIENT_ERROR();
                }

                fReplyRequired = true;
                pbOptReplyData = (PBYTE)&frameContext;
                cbOptReplyData = sizeof(frameContext);
#endif // RIGHT_SIDE_COMPILE
                break;

            case MT_GetDCB:
#ifdef RIGHT_SIDE_COMPILE
                if (!ProcessReply(&sReceiveHeader))
                    HANDLE_TRANSIENT_ERROR();
#else // RIGHT_SIDE_COMPILE
                fReplyRequired = true;
                MarshalDCBToDCBTransport(m_pDCB, &dcbt);
                pbOptReplyData = (PBYTE)&dcbt;
                cbOptReplyData = sizeof(DebuggerIPCControlBlockTransport);
#endif // RIGHT_SIDE_COMPILE
                break;

            case MT_SetDCB:
#ifdef RIGHT_SIDE_COMPILE
                if (!ProcessReply(&sReceiveHeader))
                    HANDLE_TRANSIENT_ERROR();
#else // RIGHT_SIDE_COMPILE
                if (sReceiveHeader.m_cbDataBlock != (DWORD)sizeof(DebuggerIPCControlBlockTransport))
                {
                    _ASSERTE(!"Inconsistent SetDCB request");
                    HANDLE_CRITICAL_ERROR();
                }

                fReplyRequired = true;

                if (!ReceiveBlock((PBYTE)&dcbt, sizeof(DebuggerIPCControlBlockTransport)))
                    HANDLE_TRANSIENT_ERROR();

                MarshalDCBTransportToDCB(&dcbt, m_pDCB);
#endif // RIGHT_SIDE_COMPILE
                break;

            case MT_GetAppDomainCB:
#ifdef RIGHT_SIDE_COMPILE
                if (!ProcessReply(&sReceiveHeader))
                    HANDLE_TRANSIENT_ERROR();
#else // RIGHT_SIDE_COMPILE
                fReplyRequired = true;
                pbOptReplyData = (PBYTE)m_pADB;
                cbOptReplyData = sizeof(AppDomainEnumerationIPCBlock);
#endif // RIGHT_SIDE_COMPILE
                break;

            default:
                _ASSERTE(!"Unknown message type");
                HANDLE_CRITICAL_ERROR();
            }

#ifndef RIGHT_SIDE_COMPILE
            // On the left side we may need to send a reply back.
            if (fReplyRequired)
            {
                Message sReply;
                sReply.Init(sReceiveHeader.m_eType, pbOptReplyData, cbOptReplyData);
                sReply.m_sHeader.m_dwReplyId = sReceiveHeader.m_dwId;
                sReply.m_sHeader.TypeSpecificData = sReceiveHeader.TypeSpecificData;

#ifdef _DEBUG
                DbgTransportLog(LC_Requests, "Sending '%s' reply", MessageName(sReceiveHeader.m_eType));
#endif // _DEBUG

                // We must be careful with the failure mode of SendMessage here to avoid the same request
                // being processed too many or too few times. See the comment above starting with 'IMPORTANT
                // NOTE' for more details. The upshot is that on SendMessage hresult failures (which indicate
                // the message will never be sent), we don't update m_dwLastMessageIdSeen and simply wait for
                // the request to be made again. When we get success, however, we must be careful to ensure
                // that m_dwLastMessageIdSeen gets updated even if a network error is reported. Otherwise on
                // the resync we'll both reprocess the request and re-send the original reply which is very
                // very bad.
                hr = SendMessage(&sReply, false);

                if (FAILED(hr))
                    HANDLE_TRANSIENT_ERROR(); // Message will never be sent, other side will retry

                // SendMessage doesn't report network errors (it simply queues the send and changes the
                // session state). So check for a network error here specifically so we can get started on the
                // resync. We must update m_dwLastMessageIdSeen first though, or the other side will retry the
                // request.
                if (m_eState != SS_Open)
                {
                    _ASSERTE(sReceiveHeader.m_dwId > m_dwLastMessageIdSeen);
                    m_dwLastMessageIdSeen = sReceiveHeader.m_dwId;
                    HANDLE_TRANSIENT_ERROR();
                }
            }
#endif // !RIGHT_SIDE_COMPILE

            if (sReceiveHeader.m_dwId != (DWORD)0)
            {
                // We've now completed processing on the incoming message. Remember we've processed up to this
                // message ID so that on a resync the other side doesn't send it to us again.
                _ASSERTE(sReceiveHeader.m_dwId > m_dwLastMessageIdSeen);
                m_dwLastMessageIdSeen = sReceiveHeader.m_dwId;
            }
        }
    }

  Shutdown:

    _ASSERTE(m_eState == SS_Closed);

#ifdef RIGHT_SIDE_COMPILE
    // The session is definitely not open at this point.
    ResetEvent(m_hSessionOpenEvent);
#endif // RIGHT_SIDE_COMPILE

    // Close the connection if we haven't done so already.
    m_pipe.Disconnect();

    // Drain any remaining entries in the send queue (aborting them when they need completions).
    {
        TransportLockHolder sLockHolder(&m_sStateLock);

        Message *pMsg;
        while ((pMsg = m_pSendQueueFirst) != NULL)
        {
            // Remove message from the queue.
            m_pSendQueueFirst = pMsg->m_pNext;

            // Determine whether the message needs to be deleted by us before we signal any completion (because
            // once we signal the completion pMsg might become invalid immediately if it's not a copy).
            bool fMustDelete = pMsg->m_pOrigMessage != pMsg;

            // If there's a waiter (i.e. we don't own the message) it know that the operation didn't really
            // complete, it was aborted.
            if (!fMustDelete)
                pMsg->m_pOrigMessage->m_fAborted = true;

            // Determine how to complete the message.
            switch (pMsg->m_sHeader.m_eType)
            {
            case MT_SessionRequest:
            case MT_SessionAccept:
            case MT_SessionReject:
            case MT_SessionResync:
            case MT_SessionClose:
                _ASSERTE(!"Session management messages should not be on send queue");
                break;

            case MT_Event:
                break;

#ifdef RIGHT_SIDE_COMPILE
            case MT_ReadMemory:
            case MT_WriteMemory:
            case MT_VirtualUnwind:
            case MT_GetDCB:
            case MT_SetDCB:
            case MT_GetAppDomainCB:
                // On the RS these are the original requests. Signal the completion event.
                SignalReplyEvent(pMsg);
                break;
#else // RIGHT_SIDE_COMPILE
            case MT_ReadMemory:
            case MT_WriteMemory:
            case MT_VirtualUnwind:
            case MT_GetDCB:
            case MT_SetDCB:
            case MT_GetAppDomainCB:
                // On the LS these are replies to the original request. Nobody's waiting on these.
                break;
#endif // RIGHT_SIDE_COMPILE

            default:
                _ASSERTE(!"Unknown message type");
            }

            // If the message was a copy, deallocate the resources now.
            if (fMustDelete)
            {
                if (pMsg->m_pbDataBlock)
                    delete [] pMsg->m_pbDataBlock;
                delete pMsg;
            }
        }
    } // Leave m_sStateLock

    // Now release all the resources allocated for the transport now that the
    // worker thread isn't using them anymore.
    Release();
}