Skip to content

Commit

Permalink
Report errors processing messages from the build host (#76252)
Browse files Browse the repository at this point in the history
Related to #75967

Previously, any errors processing messages would just cause an inifnite
hang. This is updates the implementation to do two things
1.  Catch the specific string that failed to be deserialized (if any)
2. If processing failed for any reason, report the exception to the
outstanding requests and trigger disconnect.
  • Loading branch information
dibarbet authored Dec 4, 2024
2 parents 56e066c + 24d261f commit 9bf65ae
Showing 1 changed file with 39 additions and 22 deletions.
61 changes: 39 additions & 22 deletions src/Workspaces/Core/MSBuild/Rpc/RpcClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,46 +46,63 @@ public void Start()
// We'll start this and let it run until Shutdown has been called.
Task.Run(async () =>
{
string? line;
while ((line = await _receivingStream.TryReadLineOrReturnNullIfCancelledAsync(_shutdownTokenSource.Token).ConfigureAwait(false)) != null)
Exception? processingException = null;
try
{
var response = JsonConvert.DeserializeObject<Response>(line);
string? line;
while ((line = await _receivingStream.TryReadLineOrReturnNullIfCancelledAsync(_shutdownTokenSource.Token).ConfigureAwait(false)) != null)
{
Response? response;
try
{
response = JsonConvert.DeserializeObject<Response>(line);
}
catch (JsonException ex)
{
var message = $"Failed to deserialize response from build host:{Environment.NewLine}{line}";
throw new AggregateException(message, ex);
}

Contract.ThrowIfNull(response);
Contract.ThrowIfNull(response);

Contract.ThrowIfFalse(_outstandingRequests.TryRemove(response.Id, out var completionSourceAndExpectedType), $"We got a response for request ID {response.Id} but that was already completed.");
var (completionSource, expectedType) = completionSourceAndExpectedType;
Contract.ThrowIfFalse(_outstandingRequests.TryRemove(response.Id, out var completionSourceAndExpectedType), $"We got a response for request ID {response.Id} but that was already completed.");
var (completionSource, expectedType) = completionSourceAndExpectedType;

if (response.Exception != null)
{
completionSource.SetException(new RemoteInvocationException(response.Exception));
}
else
{
// If this is void-returning, then just set null
if (expectedType == null)
if (response.Exception != null)
{
completionSource.SetResult(null);
completionSource.SetException(new RemoteInvocationException(response.Exception));
}
else
{
try
// If this is void-returning, then just set null
if (expectedType == null)
{
// response.Value might be null if the response was in fact null.
var result = response.Value?.ToObject(expectedType);
completionSource.SetResult(result);
completionSource.SetResult(null);
}
catch (Exception ex)
else
{
completionSource.SetException(new Exception("Unable to deserialize the result", ex));
try
{
// response.Value might be null if the response was in fact null.
var result = response.Value?.ToObject(expectedType);
completionSource.SetResult(result);
}
catch (Exception ex)
{
completionSource.SetException(new Exception("Unable to deserialize the result", ex));
}
}
}
}
}
catch (Exception ex)
{
processingException = ex;
}

// We've disconnected, so cancel any remaining outstanding tasks
foreach (var (request, _) in _outstandingRequests.Values)
request.SetException(new System.Exception("The server disconnected unexpectedly."));
request.SetException(processingException ?? new System.Exception("The server disconnected unexpectedly."));

Disconnected?.Invoke(this, EventArgs.Empty);
});
Expand Down

0 comments on commit 9bf65ae

Please sign in to comment.