Skip to content

Commit f08e881

Browse files
rokonecAR-May
andauthored
Concurrency bug fix - BuildManager instances acquire its own BuildTelemetry instance (#8561)
* BuildManager instances acquire its own BuildTelemetry instance (#8444) Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1708215 Context In VS there are multiple instances of BuildManager called asynchronously. For DTB and normal build and maybe other which I have not identified yet. Changes Made BuildManager instances acquire its own BuildTelemetry instance as oppose to sharing single BuildTelemetry instance in non thread safe manner. Testing Locally # Conflicts: # src/Build/BackEnd/Client/MSBuildClient.cs - resolved with minimal and safe approach * Bumping version * Turn off static graph restore. (#8498) Our CI builds fails because of bug NuGet/Home#12373. It is fixed in NuGet/NuGet.Client#5010. We are waiting for it to flow to CI machines. Meanwhile this PR applies a workaround. Note: This PR needs to be reverted once it happens. --------- Co-authored-by: AR-May <[email protected]>
1 parent 2eac915 commit f08e881

File tree

9 files changed

+72
-52
lines changed

9 files changed

+72
-52
lines changed

eng/Build.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the MIT license. See License.txt in the project root for full license information. -->
33
<Project>
44

5+
<!-- Commented out as a temporary fix for the msbuild CI.
6+
Waiting for https://github.com/NuGet/NuGet.Client/pull/5010 fix to flow to CI machines. -->
7+
<!--
58
<PropertyGroup>
69
<RestoreUseStaticGraphEvaluation Condition="'$(DotNetBuildFromSource)' != 'true'">true</RestoreUseStaticGraphEvaluation>
710
</PropertyGroup>
11+
-->
812

913
<ItemGroup>
1014
<!-- Remove all sln files globbed by arcade so far and add only MSBuild.sln to the build.

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the MIT license. See License.txt in the project root for full license information. -->
33
<Project>
44
<PropertyGroup>
5-
<VersionPrefix>17.4.1</VersionPrefix><DotNetFinalVersionKind>release</DotNetFinalVersionKind>
5+
<VersionPrefix>17.4.2</VersionPrefix><DotNetFinalVersionKind>release</DotNetFinalVersionKind>
66
<AssemblyVersion>15.1.0.0</AssemblyVersion>
77
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>
88
<DotNetUseShippingVersions>true</DotNetUseShippingVersions>

src/Build.UnitTests/BackEnd/KnownTelemetry_Tests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ public class KnownTelemetry_Tests
1515
[Fact]
1616
public void BuildTelemetryCanBeSetToNull()
1717
{
18-
KnownTelemetry.BuildTelemetry = new BuildTelemetry();
19-
KnownTelemetry.BuildTelemetry = null;
18+
KnownTelemetry.PartialBuildTelemetry = new BuildTelemetry();
19+
KnownTelemetry.PartialBuildTelemetry = null;
2020

21-
KnownTelemetry.BuildTelemetry.ShouldBeNull();
21+
KnownTelemetry.PartialBuildTelemetry.ShouldBeNull();
2222
}
2323

2424
[Fact]
2525
public void BuildTelemetryCanBeSet()
2626
{
2727
BuildTelemetry buildTelemetry = new BuildTelemetry();
28-
KnownTelemetry.BuildTelemetry = buildTelemetry;
28+
KnownTelemetry.PartialBuildTelemetry = buildTelemetry;
2929

30-
KnownTelemetry.BuildTelemetry.ShouldBeSameAs(buildTelemetry);
30+
KnownTelemetry.PartialBuildTelemetry.ShouldBeSameAs(buildTelemetry);
3131
}
3232

3333
[Fact]

src/Build/BackEnd/BuildManager/BuildManager.cs

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ public class BuildManager : INodePacketHandler, IBuildComponentHost, IDisposable
253253

254254
private IEnumerable<DeferredBuildMessage> _deferredBuildMessages;
255255

256+
/// <summary>
257+
/// Build telemetry to be send when this build ends.
258+
/// <remarks>Could be null</remarks>
259+
/// </summary>
260+
private BuildTelemetry _buildTelemetry;
261+
256262
private ProjectCacheService _projectCacheService;
257263

258264
private bool _hasProjectCacheServiceInitializedVsScenario;
@@ -491,11 +497,22 @@ public void BeginBuild(BuildParameters parameters)
491497

492498
// Initiate build telemetry data
493499
DateTime now = DateTime.UtcNow;
494-
KnownTelemetry.BuildTelemetry ??= new()
500+
501+
// Acquire it from static variable so we can apply data collected up to this moment
502+
_buildTelemetry = KnownTelemetry.PartialBuildTelemetry;
503+
if (_buildTelemetry != null)
504+
{
505+
KnownTelemetry.PartialBuildTelemetry = null;
506+
}
507+
else
495508
{
496-
StartAt = now,
497-
};
498-
KnownTelemetry.BuildTelemetry.InnerStartAt = now;
509+
_buildTelemetry = new()
510+
{
511+
StartAt = now,
512+
};
513+
}
514+
515+
_buildTelemetry.InnerStartAt = now;
499516

500517
if (BuildParameters.DumpOpportunisticInternStats)
501518
{
@@ -805,10 +822,10 @@ public BuildSubmission PendBuildRequest(BuildRequestData requestData)
805822

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

808-
if (KnownTelemetry.BuildTelemetry != null)
825+
if (_buildTelemetry != null)
809826
{
810-
KnownTelemetry.BuildTelemetry.Project ??= requestData.ProjectFullPath;
811-
KnownTelemetry.BuildTelemetry.Target ??= string.Join(",", requestData.TargetNames);
827+
_buildTelemetry.Project ??= requestData.ProjectFullPath;
828+
_buildTelemetry.Target ??= string.Join(",", requestData.TargetNames);
812829
}
813830

814831
_buildSubmissions.Add(newSubmission.SubmissionId, newSubmission);
@@ -833,12 +850,12 @@ public GraphBuildSubmission PendBuildRequest(GraphBuildRequestData requestData)
833850

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

836-
if (KnownTelemetry.BuildTelemetry != null)
853+
if (_buildTelemetry != null)
837854
{
838855
// Project graph can have multiple entry points, for purposes of identifying event for same build project,
839856
// we believe that including only one entry point will provide enough precision.
840-
KnownTelemetry.BuildTelemetry.Project ??= requestData.ProjectGraphEntryPoints?.FirstOrDefault().ProjectFile;
841-
KnownTelemetry.BuildTelemetry.Target ??= string.Join(",", requestData.TargetNames);
857+
_buildTelemetry.Project ??= requestData.ProjectGraphEntryPoints?.FirstOrDefault().ProjectFile;
858+
_buildTelemetry.Target ??= string.Join(",", requestData.TargetNames);
842859
}
843860

844861
_graphBuildSubmissions.Add(newSubmission.SubmissionId, newSubmission);
@@ -987,13 +1004,13 @@ public void EndBuild()
9871004

9881005
loggingService.LogBuildFinished(_overallBuildSuccess);
9891006

990-
if (KnownTelemetry.BuildTelemetry != null)
1007+
if (_buildTelemetry != null)
9911008
{
992-
KnownTelemetry.BuildTelemetry.FinishedAt = DateTime.UtcNow;
993-
KnownTelemetry.BuildTelemetry.Success = _overallBuildSuccess;
994-
KnownTelemetry.BuildTelemetry.Version = ProjectCollection.Version;
995-
KnownTelemetry.BuildTelemetry.DisplayVersion = ProjectCollection.DisplayVersion;
996-
KnownTelemetry.BuildTelemetry.FrameworkName = NativeMethodsShared.FrameworkName;
1009+
_buildTelemetry.FinishedAt = DateTime.UtcNow;
1010+
_buildTelemetry.Success = _overallBuildSuccess;
1011+
_buildTelemetry.Version = ProjectCollection.Version;
1012+
_buildTelemetry.DisplayVersion = ProjectCollection.DisplayVersion;
1013+
_buildTelemetry.FrameworkName = NativeMethodsShared.FrameworkName;
9971014

9981015
string host = null;
9991016
if (BuildEnvironmentState.s_runningInVisualStudio)
@@ -1008,12 +1025,12 @@ public void EndBuild()
10081025
{
10091026
host = "VSCode";
10101027
}
1011-
KnownTelemetry.BuildTelemetry.Host = host;
1028+
_buildTelemetry.Host = host;
10121029

1013-
KnownTelemetry.BuildTelemetry.UpdateEventProperties();
1014-
loggingService.LogTelemetry(buildEventContext: null, KnownTelemetry.BuildTelemetry.EventName, KnownTelemetry.BuildTelemetry.Properties);
1030+
_buildTelemetry.UpdateEventProperties();
1031+
loggingService.LogTelemetry(buildEventContext: null, _buildTelemetry.EventName, _buildTelemetry.Properties);
10151032
// Clean telemetry to make it ready for next build submission.
1016-
KnownTelemetry.BuildTelemetry = null;
1033+
_buildTelemetry = null;
10171034
}
10181035
}
10191036

src/Build/BackEnd/Client/MSBuildClient.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,9 @@ public MSBuildClientExitResult Execute(CancellationToken cancellationToken)
166166

167167
CommunicationsUtilities.Trace("Executing build with command line '{0}'", descriptiveCommandLine);
168168
bool serverIsAlreadyRunning = ServerIsRunning();
169-
if (KnownTelemetry.BuildTelemetry != null)
169+
if (KnownTelemetry.PartialBuildTelemetry != null)
170170
{
171-
KnownTelemetry.BuildTelemetry.InitialServerState = serverIsAlreadyRunning ? "hot" : "cold";
171+
KnownTelemetry.PartialBuildTelemetry.InitialServerState = serverIsAlreadyRunning ? "hot" : "cold";
172172
}
173173
if (!serverIsAlreadyRunning)
174174
{
@@ -524,14 +524,14 @@ private ServerNodeBuildCommand GetServerNodeBuildCommand()
524524
// We remove env variable used to invoke MSBuild server as that might be equal to 1, so we do not get an infinite recursion here.
525525
envVars.Remove(Traits.UseMSBuildServerEnvVarName);
526526

527-
Debug.Assert(KnownTelemetry.BuildTelemetry == null || KnownTelemetry.BuildTelemetry.StartAt.HasValue, "BuildTelemetry.StartAt was not initialized!");
527+
Debug.Assert(KnownTelemetry.PartialBuildTelemetry == null || KnownTelemetry.PartialBuildTelemetry.StartAt.HasValue, "BuildTelemetry.StartAt was not initialized!");
528528

529-
PartialBuildTelemetry? partialBuildTelemetry = KnownTelemetry.BuildTelemetry == null
529+
PartialBuildTelemetry? partialBuildTelemetry = KnownTelemetry.PartialBuildTelemetry == null
530530
? null
531531
: new PartialBuildTelemetry(
532-
startedAt: KnownTelemetry.BuildTelemetry.StartAt.GetValueOrDefault(),
533-
initialServerState: KnownTelemetry.BuildTelemetry.InitialServerState,
534-
serverFallbackReason: KnownTelemetry.BuildTelemetry.ServerFallbackReason);
532+
startedAt: KnownTelemetry.PartialBuildTelemetry.StartAt.GetValueOrDefault(),
533+
initialServerState: KnownTelemetry.PartialBuildTelemetry.InitialServerState,
534+
serverFallbackReason: KnownTelemetry.PartialBuildTelemetry.ServerFallbackReason);
535535

536536
return new ServerNodeBuildCommand(
537537
_commandLine,

src/Build/BackEnd/Node/OutOfProcServerNode.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -365,15 +365,13 @@ private void HandleServerNodeBuildCommand(ServerNodeBuildCommand command)
365365
ConsoleConfiguration.Provider = command.ConsoleConfiguration;
366366

367367
// Initiate build telemetry
368-
if (KnownTelemetry.BuildTelemetry == null)
369-
{
370-
KnownTelemetry.BuildTelemetry = new BuildTelemetry();
371-
}
372368
if (command.PartialBuildTelemetry != null)
373369
{
374-
KnownTelemetry.BuildTelemetry.StartAt = command.PartialBuildTelemetry.StartedAt;
375-
KnownTelemetry.BuildTelemetry.InitialServerState = command.PartialBuildTelemetry.InitialServerState;
376-
KnownTelemetry.BuildTelemetry.ServerFallbackReason = command.PartialBuildTelemetry.ServerFallbackReason;
370+
BuildTelemetry buildTelemetry = KnownTelemetry.PartialBuildTelemetry ??= new BuildTelemetry();
371+
372+
buildTelemetry.StartAt = command.PartialBuildTelemetry.StartedAt;
373+
buildTelemetry.InitialServerState = command.PartialBuildTelemetry.InitialServerState;
374+
buildTelemetry.ServerFallbackReason = command.PartialBuildTelemetry.ServerFallbackReason;
377375
}
378376

379377
// Also try our best to increase chance custom Loggers which use Console static members will work as expected.

src/Framework/Telemetry/KnownTelemetry.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ namespace Microsoft.Build.Framework.Telemetry;
99
internal static class KnownTelemetry
1010
{
1111
/// <summary>
12-
/// Telemetry for build.
13-
/// If null Telemetry is not supposed to be emitted.
14-
/// After telemetry is emitted, sender shall null it.
12+
/// Partial Telemetry for build.
13+
/// This could be optionally initialized with some values from early in call stack, for example in Main method.
14+
/// After this instance is acquired by a particular build, this is set to null.
15+
/// Null means there are no prior collected build telemetry data, new clean instance shall be created for particular build.
1516
/// </summary>
16-
public static BuildTelemetry? BuildTelemetry { get; set; }
17+
public static BuildTelemetry? PartialBuildTelemetry { get; set; }
1718
}

src/MSBuild/MSBuildClientApp.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ public static MSBuildApp.ExitType Execute(
7878
exitResult.MSBuildClientExitType == MSBuildClientExitType.UnableToConnect ||
7979
exitResult.MSBuildClientExitType == MSBuildClientExitType.LaunchError)
8080
{
81-
if (KnownTelemetry.BuildTelemetry != null)
81+
if (KnownTelemetry.PartialBuildTelemetry != null)
8282
{
83-
KnownTelemetry.BuildTelemetry.ServerFallbackReason = exitResult.MSBuildClientExitType.ToString();
83+
KnownTelemetry.PartialBuildTelemetry.ServerFallbackReason = exitResult.MSBuildClientExitType.ToString();
8484
}
8585

8686
// Server is busy, fallback to old behavior.

src/MSBuild/XMake.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ string[] args
220220
DebuggerLaunchCheck();
221221

222222
// Initialize new build telemetry and record start of this build.
223-
KnownTelemetry.BuildTelemetry = new BuildTelemetry { StartAt = DateTime.UtcNow };
223+
KnownTelemetry.PartialBuildTelemetry = new BuildTelemetry { StartAt = DateTime.UtcNow };
224224

225225
using PerformanceLogEventListener eventListener = PerformanceLogEventListener.Create();
226226

@@ -308,18 +308,18 @@ string[] commandLine
308308
IsInteractiveBuild(commandLineSwitches))
309309
{
310310
canRunServer = false;
311-
if (KnownTelemetry.BuildTelemetry != null)
311+
if (KnownTelemetry.PartialBuildTelemetry != null)
312312
{
313-
KnownTelemetry.BuildTelemetry.ServerFallbackReason = "Arguments";
313+
KnownTelemetry.PartialBuildTelemetry.ServerFallbackReason = "Arguments";
314314
}
315315
}
316316
}
317317
catch (Exception ex)
318318
{
319319
CommunicationsUtilities.Trace("Unexpected exception during command line parsing. Can not determine if it is allowed to use Server. Fall back to old behavior. Exception: {0}", ex);
320-
if (KnownTelemetry.BuildTelemetry != null)
320+
if (KnownTelemetry.PartialBuildTelemetry != null)
321321
{
322-
KnownTelemetry.BuildTelemetry.ServerFallbackReason = "ErrorParsingCommandLine";
322+
KnownTelemetry.PartialBuildTelemetry.ServerFallbackReason = "ErrorParsingCommandLine";
323323
}
324324
canRunServer = false;
325325
}
@@ -631,7 +631,7 @@ string[] commandLine
631631
DebuggerLaunchCheck();
632632

633633
// Initialize new build telemetry and record start of this build, if not initialized already
634-
KnownTelemetry.BuildTelemetry ??= new BuildTelemetry { StartAt = DateTime.UtcNow };
634+
KnownTelemetry.PartialBuildTelemetry ??= new BuildTelemetry { StartAt = DateTime.UtcNow };
635635

636636
// Indicate to the engine that it can toss extraneous file content
637637
// when it loads microsoft.*.targets. We can't do this in the general case,

0 commit comments

Comments
 (0)