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