Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b0a788d
[WIP-FEATURE] MSBuild server node (#7489)
MichalPavlik Apr 21, 2022
3c24e9a
Removed IHanshake interface and removed duplicate code from ServerNod…
MichalPavlik Apr 25, 2022
3be5f95
[WIP-FEATURE] MSBuild client (#7540)
AR-May Apr 28, 2022
cdacf78
Change server mutex name generationt to support posix
rokonec May 3, 2022
e0a7bc8
Fix appending FORCECONSOLECOLOR log parametr
rokonec May 9, 2022
f97f022
Server instrumentation (#7602)
Forgind May 16, 2022
5c2eead
Added cancelation feature (#7638)
MichalPavlik May 24, 2022
5ade626
Fix control sequence emission (#7630)
Forgind May 30, 2022
8080b4c
Solving memory leak by reusing BuildManager and ProjectRoolElementCache
rokonec May 27, 2022
e41cf8a
Do not clear project root element cache if in auto reload.
rokonec May 27, 2022
447225c
Reduce if
rokonec May 27, 2022
836f6ef
Solving memory leak by reusing BuildManager and ProjectRoolElementCac…
rokonec Jun 2, 2022
88f571c
Put msbuild server feature under ChangeWave 17.4. (#7661)
AR-May Jun 2, 2022
88f3410
Add support for MSBUILDDEBUGONSTART env. var. for msbuild server clie…
AR-May Jun 2, 2022
b4a3482
Fix msbuild server process launch. (#7673)
AR-May Jun 6, 2022
206f918
Add test for MSBuild Server (#7592)
Forgind Jun 7, 2022
c2691b1
Merge branch 'feature/msbuild-server' of https://github.com/dotnet/ms…
rokonec Jun 7, 2022
90d6f32
Added cancelation support for client. (#7659)
MichalPavlik Jun 7, 2022
4b42b69
Fix graceful disconnection (#7701)
rokonec Jun 16, 2022
39a56d0
Moving public types not intended to use externally to "Microsoft.Buil…
MichalPavlik Jun 16, 2022
60179f8
Merge branch 'feature/msbuild-server' of https://github.com/dotnet/ms…
rokonec Jun 20, 2022
1c9ed27
Propagete Console properties to MSBuild Server (#7683)
rokonec Jun 20, 2022
c994838
Merge branch 'feature/msbuild-server' of https://github.com/dotnet/ms…
rokonec Jun 21, 2022
dec0fbc
Fix arguments passing to MSBuild Server (#7723)
rokonec Jun 21, 2022
324efc5
Merge branch 'main' into feature/msbuild-server
AR-May Jun 23, 2022
b92eeef
Fix wrong merge.
AR-May Jun 23, 2022
8d2ea0f
Make logging asynchronous
AR-May Jun 24, 2022
8904e83
Merge branch 'feature/msbuild-server' of https://github.com/dotnet/ms…
rokonec Jun 24, 2022
981a7bd
Update src/Build/BackEnd/Client/MSBuildClient.cs
MichalPavlik Jun 28, 2022
93c62a3
MSBuild Server: Always run MSBuild nodes with MSBUILDUSESERVER disabl…
AR-May Jun 28, 2022
8032e69
Fix copyright info. (#7758)
AR-May Jun 29, 2022
200fbb9
Fixed some commented issues (#7759)
MichalPavlik Jun 30, 2022
23fb4a6
Update doc (#7740)
MichalPavlik Jul 1, 2022
a55907e
Merge branch 'main' into feature/msbuild-server
AR-May Jul 1, 2022
7a293b2
Added argument null checking
rokonec Jul 11, 2022
c9a1e61
Delete dead commented code
rokonec Jul 11, 2022
072b447
Delete dead code
rokonec Jul 11, 2022
265f3b7
prefix mutexes name by msbuild
rokonec Jul 11, 2022
2aa65ba
Reuse ConnectToPipeStream from NodeProviderOutOfProcBase
rokonec Jul 11, 2022
ca8f983
Merge branch 'feature/msbuild-server' of https://github.com/dotnet/ms…
rokonec Jul 11, 2022
e45470a
Merge branch 'main' of https://github.com/dotnet/msbuild into feature…
rokonec Jul 11, 2022
504f720
Unshipped api fix
rokonec Jul 11, 2022
c625fc1
Reduces allocations in case the tracing is not enabled (#7814)
MichalPavlik Jul 12, 2022
6d00e2b
Fix msbuild client exit type for connection errors. (#7816)
AR-May Jul 12, 2022
26b43b0
Emmit BuildTelemetry event (#7778)
rokonec Jul 12, 2022
60abe1f
Increase console refresh to 25Hz
rokonec Jul 12, 2022
ae46347
Do not recover from TrySendBuildCommand failure
rokonec Jul 13, 2022
9c2f0f3
Move *ConcoleConfiguration classes to its own files
rokonec Jul 13, 2022
aa17a3f
Change test timing
rokonec Jul 13, 2022
b87a5be
Add comment for graph build telemetry project
rokonec Jul 13, 2022
568e61f
Fix English in comment
rokonec Jul 13, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions documentation/MSBuild-Server.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
# MSBuild Server

MSBuild Server is basically an another type of node which can accept build request from clients and utilize worker nodes in current fashion to build projects. Main purpose of the server node is to avoid expensive MSBuild process start during build from tools like .NET SDK.
MSBuild Server nodes accept build requests from clients and use worker nodes in the current fashion to build projects. The main purpose of the server node is to preserve caches between builds and avoid expensive MSBuild process start operations during build from tools like the .NET SDK.

## Usage

The primary ways to use MSBuild are via Visual Studio and via the CLI using the `dotnet build`/`dotnet msbuild` commands. MSBuild Server is not supported in Visual Studio because Visual Studio itself works like MSBuild Server. For the CLI, the server functionality is enabled by default and can be disabled by setting the `DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER` environment variable to value `1`.
To re-enable MSBuild Server, remove the variable or set its value to `0`.

## Communication protocol

The server node uses same IPC approach as current worker nodes - named pipes. This solution allows to reuse existing code. When process starts, pipe with deterministic name is opened and waiting for commands. Client has following worfklow:

1. Try to connect to server
- If server is not running, start new instance
- If server is busy, fallback to classic build
- If server is busy or the connection is broken, fall back to previous build behavior
2. Initiate handshake
2. Issue build command with `EntryNodeCommand` packet
2. Issue build command with `ServerNodeBuildCommand` packet
3. Read packets from pipe
- When `EntryNodeConsoleWrite` packet type is recieved, write content to appropriate output stream with respected coloring
- When `EntryNodeResponse` packet type is recieved, build is done and client writes trace message with exit code
- Write content to the appropriate output stream (respecting coloring) with the `ServerNodeConsoleWrite` packet
- After the build completes, the `ServerNodeBuildResult` packet indicates the exit code

### Pipe name convention & handshake

Expand All @@ -25,7 +30,7 @@ Handshake is a procedure ensuring that client is connecting to a compatible serv

Server requires to introduce new packet types for IPC.

`EntryNodeCommand` contains all of the information necessary for a server to run a build.
`ServerNodeBuildCommand` contains all of the information necessary for a server to run a build.

| Property name | Type | Description |
|---|---|---|
Expand All @@ -34,21 +39,22 @@ Server requires to introduce new packet types for IPC.
| BuildProcessEnvironment | IDictionary<String, String> | Environment variables for current build |
| Culture | CultureInfo | The culture value for current build |
| UICulture | CultureInfo | The UI culture value for current build |
| ConsoleConfiguration | TargetConsoleConfiguration | Console configuration of target Console at which the output will be rendered |

`EntryNodeConsoleWrite` contains information for console output.
`ServerNodeConsoleWrite` contains information for console output.

| Property name | Type | Description |
|---|---|---|
| Text | String | The text that is written to the output stream. It includes ANSI escape codes for formatting. |
| OutputType | Byte | Identification of the output stream (1 = standard output, 2 = error output) |

`EntryNodeResponse` informs about finished build.
`ServerNodeBuildResult` indicates how the build finished.

| Property name | Type | Description |
|---|---|---|
| ExitCode | Int32 | The exit code of the build |
| ExitType | String | The exit type of the build |

`EntryNodeCancel` cancels the current build.
`ServerNodeBuildCancel` cancels the current build.

This type is intentionally empty and properties for build cancelation could be added in future.
1 change: 1 addition & 0 deletions documentation/specs/event-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ EventSource is primarily used to profile code. For MSBuild specifically, a major
| GenerateResourceOverall | Uses resource APIs to transform resource files into strongly-typed resource classes. |
| LoadDocument | Loads an XMLDocumentWithLocation from a path.
| MSBuildExe | Executes MSBuild from the command line. |
| MSBuildServerBuild | Executes a build from the MSBuildServer node. |
| PacketReadSize | Reports the size of a packet sent between nodes. Note that this does not include time information. |
| Parse | Parses an XML document into a ProjectRootElement. |
| ProjectGraphConstruction | Constructs a dependency graph among projects. |
Expand Down
1 change: 1 addition & 0 deletions documentation/wiki/ChangeWaves.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ A wave of features is set to "rotate out" (i.e. become standard functionality) t
- [Respect deps.json when loading assemblies](https://github.com/dotnet/msbuild/pull/7520)
- [Consider `Platform` as default during Platform Negotiation](https://github.com/dotnet/msbuild/pull/7511)
- [Adding accepted SDK name match pattern to SDK manifests](https://github.com/dotnet/msbuild/pull/7597)
- [MSBuild server](https://github.com/dotnet/msbuild/pull/7634)

### 17.0
- [Scheduler should honor BuildParameters.DisableInprocNode](https://github.com/dotnet/msbuild/pull/6400)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\Shared\FileSystemSources.proj" />
<Import Project="..\Shared\DebuggingSources.proj" />
Expand Down Expand Up @@ -78,8 +78,8 @@
<Compile Include="..\Shared\UnitTests\TestData\GlobbingTestData.cs">
<Link>TestData\GlobbingTestData.cs</Link>
</Compile>
<Compile Include="..\Shared\ProcessExtensions.cs" />
<Compile Include="..\UnitTests.Shared\RunnerUtilities.cs" />

<None Include="..\Shared\UnitTests\App.config">
<Link>App.config</Link>
<SubType>Designer</SubType>
Expand Down
4 changes: 1 addition & 3 deletions src/Build.OM.UnitTests/NugetRestoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
#endif
using Xunit.Abstractions;

#nullable disable

namespace Microsoft.Build.Engine.OM.UnitTests
{
public sealed class NugetRestoreTests
Expand All @@ -29,7 +27,7 @@ public NugetRestoreTests(ITestOutputHelper output)
[Fact]
public void TestOldNuget()
{
string msbuildExePath = Path.GetDirectoryName(RunnerUtilities.PathToCurrentlyRunningMsBuildExe);
string msbuildExePath = Path.GetDirectoryName(RunnerUtilities.PathToCurrentlyRunningMsBuildExe)!;
using TestEnvironment testEnvironment = TestEnvironment.Create();
TransientTestFolder folder = testEnvironment.CreateFolder(createFolder: true);
// The content of the solution isn't known to matter, but having a custom solution makes it easier to add requirements should they become evident.
Expand Down
121 changes: 121 additions & 0 deletions src/Build.UnitTests/BackEnd/KnownTelemetry_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable disable
using System;
using System.Globalization;
using Microsoft.Build.Framework.Telemetry;
using Shouldly;
using Xunit;

namespace Microsoft.Build.UnitTests.Telemetry;

public class KnownTelemetry_Tests
{
[Fact]
public void BuildTelemetryCanBeSetToNull()
{
KnownTelemetry.BuildTelemetry = new BuildTelemetry();
KnownTelemetry.BuildTelemetry = null;

KnownTelemetry.BuildTelemetry.ShouldBeNull();
}

[Fact]
public void BuildTelemetryCanBeSet()
{
BuildTelemetry buildTelemetry = new BuildTelemetry();
KnownTelemetry.BuildTelemetry = buildTelemetry;

KnownTelemetry.BuildTelemetry.ShouldBeSameAs(buildTelemetry);
}

[Fact]
public void BuildTelemetryConstructedHasNoProperties()
{
BuildTelemetry buildTelemetry = new BuildTelemetry();

buildTelemetry.DisplayVersion.ShouldBeNull();
buildTelemetry.EventName.ShouldBe("build");
buildTelemetry.FinishedAt.ShouldBeNull();
buildTelemetry.FrameworkName.ShouldBeNull();
buildTelemetry.Host.ShouldBeNull();
buildTelemetry.InitialServerState.ShouldBeNull();
buildTelemetry.InnerStartAt.ShouldBeNull();
buildTelemetry.Project.ShouldBeNull();
buildTelemetry.ServerFallbackReason.ShouldBeNull();
buildTelemetry.StartAt.ShouldBeNull();
buildTelemetry.Success.ShouldBeNull();
buildTelemetry.Target.ShouldBeNull();
buildTelemetry.Version.ShouldBeNull();

buildTelemetry.UpdateEventProperties();
buildTelemetry.Properties.ShouldBeEmpty();
}

[Fact]
public void BuildTelemetryCreateProperProperties()
{
BuildTelemetry buildTelemetry = new BuildTelemetry();

DateTime startAt = new DateTime(2023, 01, 02, 10, 11, 22);
DateTime innerStartAt = new DateTime(2023, 01, 02, 10, 20, 30);
DateTime finishedAt = new DateTime(2023, 12, 13, 14, 15, 16);

buildTelemetry.DisplayVersion = "Some Display Version";
buildTelemetry.FinishedAt = finishedAt;
buildTelemetry.FrameworkName = "new .NET";
buildTelemetry.Host = "Host description";
buildTelemetry.InitialServerState = "hot";
buildTelemetry.InnerStartAt = innerStartAt;
buildTelemetry.Project = @"C:\\dev\\theProject";
buildTelemetry.ServerFallbackReason = "busy";
buildTelemetry.StartAt = startAt;
buildTelemetry.Success = true;
buildTelemetry.Target = "clean";
buildTelemetry.Version = new Version(1, 2, 3, 4);

buildTelemetry.UpdateEventProperties();
buildTelemetry.Properties.Count.ShouldBe(11);

buildTelemetry.Properties["BuildEngineDisplayVersion"].ShouldBe("Some Display Version");
buildTelemetry.Properties["BuildEngineFrameworkName"].ShouldBe("new .NET");
buildTelemetry.Properties["BuildEngineHost"].ShouldBe("Host description");
buildTelemetry.Properties["InitialMSBuildServerState"].ShouldBe("hot");
buildTelemetry.Properties["ProjectPath"].ShouldBe(@"C:\\dev\\theProject");
buildTelemetry.Properties["ServerFallbackReason"].ShouldBe("busy");
buildTelemetry.Properties["BuildSuccess"].ShouldBe("True");
buildTelemetry.Properties["BuildTarget"].ShouldBe("clean");
buildTelemetry.Properties["BuildEngineVersion"].ShouldBe("1.2.3.4");

// verify computed
buildTelemetry.Properties["BuildDurationInMilliseconds"] = (finishedAt - startAt).TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
buildTelemetry.Properties["InnerBuildDurationInMilliseconds"] = (finishedAt - innerStartAt).TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
}

[Fact]
public void BuildTelemetryHandleNullsInRecordedTimes()
{
BuildTelemetry buildTelemetry = new BuildTelemetry();

buildTelemetry.StartAt = DateTime.MinValue;
buildTelemetry.FinishedAt = null;
buildTelemetry.UpdateEventProperties();
buildTelemetry.Properties.ShouldBeEmpty();

buildTelemetry.StartAt = null;
buildTelemetry.FinishedAt = DateTime.MaxValue;
buildTelemetry.UpdateEventProperties();
buildTelemetry.Properties.ShouldBeEmpty();

buildTelemetry.InnerStartAt = DateTime.MinValue;
buildTelemetry.FinishedAt = null;
buildTelemetry.UpdateEventProperties();
buildTelemetry.Properties.ShouldBeEmpty();

buildTelemetry.InnerStartAt = null;
buildTelemetry.FinishedAt = DateTime.MaxValue;
buildTelemetry.UpdateEventProperties();
buildTelemetry.Properties.ShouldBeEmpty();
}
}
31 changes: 31 additions & 0 deletions src/Build.UnitTests/BackEnd/RedirectConsoleWriter_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Experimental;
using Shouldly;
using Xunit;

namespace Microsoft.Build.Engine.UnitTests.BackEnd
{
public class RedirectConsoleWriter_Tests
Comment thread
rokonec marked this conversation as resolved.
{
[Fact]
public async Task EmitConsoleMessages()
Comment thread
rokonec marked this conversation as resolved.
{
StringBuilder sb = new StringBuilder();

using (TextWriter writer = OutOfProcServerNode.RedirectConsoleWriter.Create(text => sb.Append(text)))
{
writer.WriteLine("Line 1");
await Task.Delay(80); // should be somehow bigger than `RedirectConsoleWriter` flush period - see its constructor
writer.Write("Line 2");
}

sb.ToString().ShouldBe($"Line 1{Environment.NewLine}Line 2");
}
}
}
56 changes: 55 additions & 1 deletion src/Build/BackEnd/BuildManager/BuildManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using Microsoft.Build.Exceptions;
using Microsoft.Build.Experimental.ProjectCache;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Telemetry;
using Microsoft.Build.Graph;
using Microsoft.Build.Internal;
using Microsoft.Build.Logging;
Expand Down Expand Up @@ -456,7 +457,7 @@ public void BeginBuild(BuildParameters parameters)
_nodeManager?.ShutdownAllNodes();
_taskHostNodeManager?.ShutdownAllNodes();
}
}
}
}

_previousLowPriority = parameters.LowPriority;
Expand All @@ -470,6 +471,14 @@ public void BeginBuild(BuildParameters parameters)

MSBuildEventSource.Log.BuildStart();

// Initiate build telemetry data
DateTime now = DateTime.UtcNow;
KnownTelemetry.BuildTelemetry ??= new()
{
StartAt = now,
};
KnownTelemetry.BuildTelemetry.InnerStartAt = now;

if (BuildParameters.DumpOpportunisticInternStats)
{
Strings.EnableDiagnostics();
Expand Down Expand Up @@ -796,6 +805,13 @@ public BuildSubmission PendBuildRequest(BuildRequestData requestData)
VerifyStateInternal(BuildManagerState.Building);

var newSubmission = new BuildSubmission(this, GetNextSubmissionId(), requestData, _buildParameters.LegacyThreadingSemantics);

if (KnownTelemetry.BuildTelemetry != null)
{
KnownTelemetry.BuildTelemetry.Project ??= requestData.ProjectFullPath;
KnownTelemetry.BuildTelemetry.Target ??= string.Join(",", requestData.TargetNames);
}

_buildSubmissions.Add(newSubmission.SubmissionId, newSubmission);
_noActiveSubmissionsEvent.Reset();
return newSubmission;
Expand All @@ -817,6 +833,15 @@ public GraphBuildSubmission PendBuildRequest(GraphBuildRequestData requestData)
VerifyStateInternal(BuildManagerState.Building);

var newSubmission = new GraphBuildSubmission(this, GetNextSubmissionId(), requestData);

if (KnownTelemetry.BuildTelemetry != null)
{
// Project graph can have multiple entry points, for purposes of identifying event for same build project,
// we believe that including only one entry point will provide enough precision.
KnownTelemetry.BuildTelemetry.Project ??= requestData.ProjectGraphEntryPoints?.FirstOrDefault().ProjectFile;
Comment thread
rokonec marked this conversation as resolved.
KnownTelemetry.BuildTelemetry.Target ??= string.Join(",", requestData.TargetNames);
}

_graphBuildSubmissions.Add(newSubmission.SubmissionId, newSubmission);
_noActiveSubmissionsEvent.Reset();
return newSubmission;
Expand Down Expand Up @@ -965,6 +990,35 @@ public void EndBuild()
}

loggingService.LogBuildFinished(_overallBuildSuccess);

if (KnownTelemetry.BuildTelemetry != null)
{
KnownTelemetry.BuildTelemetry.FinishedAt = DateTime.UtcNow;
KnownTelemetry.BuildTelemetry.Success = _overallBuildSuccess;
KnownTelemetry.BuildTelemetry.Version = ProjectCollection.Version;
KnownTelemetry.BuildTelemetry.DisplayVersion = ProjectCollection.DisplayVersion;
KnownTelemetry.BuildTelemetry.FrameworkName = NativeMethodsShared.FrameworkName;

string host = null;
if (BuildEnvironmentState.s_runningInVisualStudio)
{
host = "VS";
}
else if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILD_HOST_NAME")))
{
host = Environment.GetEnvironmentVariable("MSBUILD_HOST_NAME");
}
else if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("VSCODE_CWD")) || Environment.GetEnvironmentVariable("TERM_PROGRAM") == "vscode")
{
host = "VSCode";
}
KnownTelemetry.BuildTelemetry.Host = host;

KnownTelemetry.BuildTelemetry.UpdateEventProperties();
loggingService.LogTelemetry(buildEventContext: null, KnownTelemetry.BuildTelemetry.EventName, KnownTelemetry.BuildTelemetry.Properties);
// Clean telemetry to make it ready for next build submission.
KnownTelemetry.BuildTelemetry = null;
}
}

ShutdownLoggingService(loggingService);
Expand Down
Loading