Skip to content

Fix silent hang in message processing loop when deserialization fails#15578

Draft
nohwnd wants to merge 2 commits intomicrosoft:mainfrom
nohwnd:investigate-hang
Draft

Fix silent hang in message processing loop when deserialization fails#15578
nohwnd wants to merge 2 commits intomicrosoft:mainfrom
nohwnd:investigate-hang

Conversation

@nohwnd
Copy link
Copy Markdown
Member

@nohwnd nohwnd commented Mar 24, 2026

When message processing throws an exception (e.g. during deserialization when System.Text.Json dependencies cannot be loaded), the message loop would either silently exit or swallow the error, leaving the caller hanging indefinitely waiting for a response that never comes.

This fix updates both DesignModeClient.ProcessRequests (vstest.console side) and TestRequestHandler.OnMessageReceived (testhost side) to send a ProtocolError message back to the caller before stopping, so the caller gets notified of the error instead of hanging.

Closes #15577

Copilot AI review requested due to automatic review settings March 24, 2026 15:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a silent hang by ensuring that when request/message processing fails (notably during deserialization), the failing side proactively notifies the peer via a ProtocolError message and then shuts down the session/loop.

Changes:

  • Wraps TestRequestHandler.OnMessageReceived in a try/catch and sends ProtocolError before closing the testhost session.
  • Updates DesignModeClient.ProcessRequests exception handling to send ProtocolError to the IDE before stopping the design-mode client loop.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs Adds a guarded message handler that sends ProtocolError on exceptions and closes the session.
src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs Sends ProtocolError to the IDE when request processing fails before stopping the client loop.

}
catch (Exception sendEx)
{
EqtTrace.Error("TestRequestHandler.OnMessageReceived: Failed to send ProtocolError: {0}", sendEx);
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description calls out failures due to missing System.Text.Json dependencies during deserialization. In that failure mode, _dataSerializer.SerializePayload(...) is likely to fail for the same reason, which would prevent ProtocolError from being sent and can reintroduce the hang. Consider a fallback error-notification path that does not depend on the JSON serializer (e.g., sending a minimal pre-serialized frame / raw transport-level error message if supported), or ensure the communication layer can emit ProtocolError even when payload serialization fails.

Suggested change
EqtTrace.Error("TestRequestHandler.OnMessageReceived: Failed to send ProtocolError: {0}", sendEx);
EqtTrace.Error("TestRequestHandler.OnMessageReceived: Failed to send ProtocolError using serializer: {0}", sendEx);
// Fallback: attempt to send a minimal, serializer-independent error message
try
{
var fallbackMessage = "ProtocolError: " + ex.ToString();
EqtTrace.Verbose("TestRequestHandler.OnMessageReceived: Sending fallback ProtocolError message without serializer: {0}", fallbackMessage);
SendData(fallbackMessage);
}
catch (Exception fallbackEx)
{
EqtTrace.Error("TestRequestHandler.OnMessageReceived: Failed to send fallback ProtocolError message: {0}", fallbackEx);
}

Copilot uses AI. Check for mistakes.
// waiting for a response that will never come.
try
{
_communicationManager.SendMessage(MessageType.ProtocolError, ex.Message);
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using ex.Message alone can be empty and loses the exception type/context, which makes the ProtocolError less actionable for the IDE/user. Consider including at least the exception type + message (e.g., ${ex.GetType().FullName}: ${ex.Message}) or a structured error payload, while being mindful of not sending overly large data (stack traces) over the protocol unless explicitly desired.

Suggested change
_communicationManager.SendMessage(MessageType.ProtocolError, ex.Message);
_communicationManager.SendMessage(
MessageType.ProtocolError,
string.Format(
CultureInfo.InvariantCulture,
"{0}: {1}",
ex.GetType().FullName,
ex.Message));

Copilot uses AI. Check for mistakes.
Comment on lines +295 to +304
// Notify the caller (IDE) about the error so it does not hang
// waiting for a response that will never come.
try
{
_communicationManager.SendMessage(MessageType.ProtocolError, ex.Message);
}
catch (Exception sendEx)
{
EqtTrace.Error("DesignModeClient: Failed to send ProtocolError to client: {0}", sendEx);
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a repeated pattern (log error → try send ProtocolError → log send failure). Since similar logic now exists on both sides (testhost + vstest.console), consider extracting a small helper (per component) like TrySendProtocolError(Exception ex) to keep exception paths consistent and reduce future drift (e.g., message formatting, telemetry, or fallback behavior).

Copilot uses AI. Check for mistakes.
@nohwnd nohwnd marked this pull request as draft March 24, 2026 15:39
@nohwnd
Copy link
Copy Markdown
Member Author

nohwnd commented Mar 24, 2026

does not make much sense to send message to caller when we most likely cannot communicate with them.

When message processing throws an exception (e.g. during deserialization
when System.Text.Json dependencies cannot be loaded), the message loop
would either silently exit or swallow the error, leaving the caller
hanging indefinitely waiting for a response that never comes.

Fix both DesignModeClient.ProcessRequests (vstest.console side) and
TestRequestHandler.OnMessageReceived (testhost side) to send a
ProtocolError message back to the caller before stopping, so the
caller gets notified of the error instead of hanging.

Closes microsoft#15577

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@nohwnd nohwnd force-pushed the investigate-hang branch from 2d1ad3f to 764b674 Compare March 24, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

STJ migration: DesignMode/wrapper communication hangs during test execution

2 participants