Skip to content

Non-blocking MSBuildClientPacketPump.#13818

Open
teo-tsirpanis wants to merge 8 commits into
dotnet:mainfrom
teo-tsirpanis:msbuild-client-packet-pump-opt
Open

Non-blocking MSBuildClientPacketPump.#13818
teo-tsirpanis wants to merge 8 commits into
dotnet:mainfrom
teo-tsirpanis:msbuild-client-packet-pump-opt

Conversation

@teo-tsirpanis
Copy link
Copy Markdown
Contributor

Context

As part of removing FEATURE_APM, I noticed that MSBuild uses old-style async I/O in various places, often leading to more complex code and threads being blocked. MSBuild Server's client is an easy first step towards modernizing MSBuild's async code.

Changes Made

  • MSBuildClientPacketPump was updated to use async/await and other modern APIs.
    • Incoming packets and errors are being communicated using System.Threading.Channels, instead of event wait handles.
    • Shutting down the loop is being communicated using CancellationToken, instead of an event wait handle.
    • We no longer spawn a dedicated thread for the packet pump.
  • MSBuildClient was updated to use asynchronous I/O to send packets and retrieve them from the packet pump's ChannelReader.
    • Added a new ExecuteAsync public API; the existing Execute method is a sync-over-async wrapper over ExecuteAsync; calling it is not recommended for performance reasons.

Testing

Existing tests pass locally, modulo some failures likely due to my machine's language being set to Greek.

Notes

@teo-tsirpanis teo-tsirpanis marked this pull request as ready for review May 21, 2026 09:37
Copilot AI review requested due to automatic review settings May 21, 2026 09:37
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

Note

Copilot was unable to run its full agentic suite in this review.

This PR refactors the MSBuild client/server communication to use async APIs and System.Threading.Channels instead of thread + wait-handle based packet pumping.

Changes:

  • Introduces async execution paths (ExecuteAsync, async packet send/receive) and sync wrappers where needed.
  • Replaces ConcurrentQueue/WaitHandle-based packet pumping with a Channel<INodePacket>-based async loop.
  • Adds System.Threading.Channels package reference to support the new channel-based implementation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
src/MSBuild/MSBuildClientApp.cs Switches client app execution to async-based implementation.
src/Build/Microsoft.Build.csproj Adds System.Threading.Channels dependency for the new packet pump.
src/Build/BackEnd/Client/MSBuildClientPacketPump.cs Replaces thread/event packet pump with channel + async task and async disposal.
src/Build/BackEnd/Client/MSBuildClient.cs Plumbs async send/receive and consumes the channel-based packet pump.

/// <returns>A value of type <see cref="MSBuildApp.ExitType"/> that indicates whether the build succeeded,
/// or the manner in which it failed.</returns>
public static MSBuildApp.ExitType Execute(string[] commandLineArgs, string msbuildLocation, CancellationToken cancellationToken)
public static async Task<MSBuildApp.ExitType> ExecuteAsync(string[] commandLineArgs, string msbuildLocation, CancellationToken cancellationToken)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This API is not public.

Comment on lines +85 to +86
public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory) =>
_packetDeserializationMethods.Add(packetType, factory);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

On second thought, removed the packet handler registering, in favor of accepting a deserialization callback in the constructor.

Comment on lines +208 to +212
_shutdownTokenSource.Cancel();
if (_packetPumpTask is not null)
{
await _packetPumpTask.ConfigureAwait(false);
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Completed _receivedPacketsChannel. There's not much reason to dispose the shutdown token source, since we don't use it for timeouts.

result.AsyncWaitHandle
while (true)
{
// Client recieved a packet header. Read the rest of it.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines +343 to +344
// After the cancelation, we want to wait to server gracefuly finish the build.
// We have to replace the cancelation token, because the thrown OCE would cause to repeatedly hit this branch of code.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

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.

2 participants