From f48f3d9db4ea9389fc9e2d6041c7148854921822 Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Tue, 25 Feb 2020 12:54:53 -0800 Subject: [PATCH 001/105] Initial draft of changes. --- .../net/Microsoft.Build.Framework.cs | 5 +++ .../netstandard/Microsoft.Build.Framework.cs | 5 +++ .../net/Microsoft.Build.Utilities.Core.cs | 1 + .../Microsoft.Build.Utilities.Core.cs | 1 + .../Components/RequestBuilder/TaskHost.cs | 33 ++++++++++++++++++- src/Build/BackEnd/Node/InProcNode.cs | 2 +- src/Build/Definition/ProjectCollection.cs | 1 + src/Framework/IBuildEngine7.cs | 28 ++++++++++++++++ src/MSBuild/XMake.cs | 4 +++ src/Tasks/Microsoft.Build.Tasks.csproj | 1 + src/Tasks/Microsoft.Common.tasks | 1 + src/Tasks/SemaphoreCPUTests.cs | 30 +++++++++++++++++ src/Utilities/Task.cs | 5 +++ 13 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 src/Framework/IBuildEngine7.cs create mode 100644 src/Tasks/SemaphoreCPUTests.cs diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs index f848650b1d0..535066ec94b 100644 --- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs @@ -197,6 +197,11 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, { System.Collections.Generic.IReadOnlyDictionary GetGlobalProperties(); } + public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 + { + void ReleaseCores(Microsoft.Build.Framework.ITask task, int releasedCores); + int RequestCores(Microsoft.Build.Framework.ITask task, int requestedCores); + } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { void Cancel(); diff --git a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs index 19a6888414a..d202cdd397d 100644 --- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs @@ -197,6 +197,11 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, { System.Collections.Generic.IReadOnlyDictionary GetGlobalProperties(); } + public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 + { + void ReleaseCores(Microsoft.Build.Framework.ITask task, int releasedCores); + int RequestCores(Microsoft.Build.Framework.ITask task, int requestedCores); + } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { void Cancel(); diff --git a/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs b/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs index d739b45082b..146d06ca8bc 100644 --- a/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs +++ b/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs @@ -352,6 +352,7 @@ public abstract partial class Task : Microsoft.Build.Framework.ITask public Microsoft.Build.Framework.IBuildEngine4 BuildEngine4 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine5 BuildEngine5 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine6 BuildEngine6 { get { throw null; } } + public Microsoft.Build.Framework.IBuildEngine7 BuildEngine7 { get { throw null; } } protected string HelpKeywordPrefix { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskHost HostObject { get { throw null; } set { } } public Microsoft.Build.Utilities.TaskLoggingHelper Log { get { throw null; } } diff --git a/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs b/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs index 1263a5949a1..fbc4b28ef38 100644 --- a/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs +++ b/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs @@ -197,6 +197,7 @@ public abstract partial class Task : Microsoft.Build.Framework.ITask public Microsoft.Build.Framework.IBuildEngine4 BuildEngine4 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine5 BuildEngine5 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine6 BuildEngine6 { get { throw null; } } + public Microsoft.Build.Framework.IBuildEngine7 BuildEngine7 { get { throw null; } } protected string HelpKeywordPrefix { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskHost HostObject { get { throw null; } set { } } public Microsoft.Build.Utilities.TaskLoggingHelper Log { get { throw null; } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index dfb430410b4..77d4d8d22eb 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -34,7 +34,7 @@ internal class TaskHost : #if FEATURE_APPDOMAIN MarshalByRefObject, #endif - IBuildEngine6 + IBuildEngine7 { /// /// True if the "secret" environment variable MSBUILDNOINPROCNODE is set. @@ -665,6 +665,37 @@ public void LogTelemetry(string eventName, IDictionary propertie #endregion + int runningTotal = 0; + + public int RequestCores(ITask task, int requestedCores) + { + Semaphore cpuCount = Semaphore.OpenExisting("cpuCount"); + int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; + // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. + for (int i = 0; i < requestedCores; i++) + { + if(cpuCount.WaitOne(0)) + { + runningTotal++; + } + else + { + break; + } + } + + return runningTotal - coresAcquiredBeforeMoreCoresGetAcquired; + } + + public void ReleaseCores(ITask task, int coresToRelease) + { + Semaphore cpuCount = Semaphore.OpenExisting("cpuCount"); + + coresToRelease = Math.Min(runningTotal, coresToRelease); + + cpuCount.Release(coresToRelease); + } + /// /// Called by the internal MSBuild task. /// Does not take the lock because it is called by another request builder thread. diff --git a/src/Build/BackEnd/Node/InProcNode.cs b/src/Build/BackEnd/Node/InProcNode.cs index b6a58044acd..f86ecad6b7c 100644 --- a/src/Build/BackEnd/Node/InProcNode.cs +++ b/src/Build/BackEnd/Node/InProcNode.cs @@ -17,7 +17,7 @@ namespace Microsoft.Build.BackEnd { /// - /// This class represents an implementation of INode for out-of-proc nodes. + /// This class represents an implementation of INode for in-proc nodes. /// internal class InProcNode : INode, INodePacketFactory { diff --git a/src/Build/Definition/ProjectCollection.cs b/src/Build/Definition/ProjectCollection.cs index cd0d6670e86..aad33d91d9c 100644 --- a/src/Build/Definition/ProjectCollection.cs +++ b/src/Build/Definition/ProjectCollection.cs @@ -28,6 +28,7 @@ using ObjectModel = System.Collections.ObjectModel; using System.Data.OleDb; using System.Runtime.CompilerServices; +using System.Security.AccessControl; namespace Microsoft.Build.Evaluation { diff --git a/src/Framework/IBuildEngine7.cs b/src/Framework/IBuildEngine7.cs new file mode 100644 index 00000000000..1bf8e0b07c8 --- /dev/null +++ b/src/Framework/IBuildEngine7.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +namespace Microsoft.Build.Framework +{ + /// + /// This interface extends IBuildEngine6 to allow tasks and build scheduler to coordinate resource (cores) usage. + /// + + public interface IBuildEngine7 : IBuildEngine6 + { + /// + /// If a task launches multiple parallel processes, it should ask how many cores it can use. + /// + /// The number of cores a task can potentially use + /// The number of cores a task is allowed to use + int RequestCores(ITask task, int requestedCores); + + /// + /// A task should notify the build manager when all or some of the requested cores are not used anymore. + /// When task is finished, the cores it requested are automatically released. + /// + /// + void ReleaseCores(ITask task, int releasedCores); + } +} diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index eb2fd268eb6..170e7e200ed 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -40,6 +40,7 @@ using LoggerDescription = Microsoft.Build.Logging.LoggerDescription; using ForwardingLoggerRecord = Microsoft.Build.Logging.ForwardingLoggerRecord; using BinaryLogger = Microsoft.Build.Logging.BinaryLogger; +using System.CodeDom; namespace Microsoft.Build.CommandLine { @@ -1174,6 +1175,8 @@ string outputResultsCache #if MSBUILDENABLEVSPROFILING DataCollection.CommentMarkProfile(8800, "Pending Build Request from MSBuild.exe"); #endif + new Semaphore(cpuCount, cpuCount, "cpuCount"); + BuildResultCode? result = null; buildManager.BeginBuild(parameters); Exception exception = null; @@ -1200,6 +1203,7 @@ string outputResultsCache else { buildRequest = new BuildRequestData(projectFile, globalProperties, toolsVersion, targets, null); + } } diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index 044ca37e250..c89e12c27e3 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -484,6 +484,7 @@ + diff --git a/src/Tasks/Microsoft.Common.tasks b/src/Tasks/Microsoft.Common.tasks index 32f8ed42f3c..68937800130 100644 --- a/src/Tasks/Microsoft.Common.tasks +++ b/src/Tasks/Microsoft.Common.tasks @@ -134,6 +134,7 @@ + diff --git a/src/Tasks/SemaphoreCPUTests.cs b/src/Tasks/SemaphoreCPUTests.cs new file mode 100644 index 00000000000..a0b8030a597 --- /dev/null +++ b/src/Tasks/SemaphoreCPUTests.cs @@ -0,0 +1,30 @@ +using Microsoft.Build.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Build.Tasks +{ + class SemaphoreCPUTests : Task + { + public override bool Execute() + { + Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(this, 3123890)} cores", Framework.MessageImportance.High); + + BuildEngine7.ReleaseCores(this, 50); + Log.LogMessageFromText("Released some number of cores", Framework.MessageImportance.High); + + Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(this, 10)} cores", Framework.MessageImportance.High); + + Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(this, 30)} cores", Framework.MessageImportance.High); + + BuildEngine7.ReleaseCores(this, 2); + Log.LogMessageFromText("Released some number of cores", Framework.MessageImportance.High); + + Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(this, 12)} cores", Framework.MessageImportance.High); + + return !Log.HasLoggedErrors; + } + } +} diff --git a/src/Utilities/Task.cs b/src/Utilities/Task.cs index 76c2d030bd8..39846721d3c 100644 --- a/src/Utilities/Task.cs +++ b/src/Utilities/Task.cs @@ -89,6 +89,11 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// public IBuildEngine6 BuildEngine6 => (IBuildEngine6)BuildEngine; + /// + /// Retrieves the version of the build engine interface provided by the host. + /// + public IBuildEngine7 BuildEngine7 => (IBuildEngine7)BuildEngine; + /// /// The build engine sets this property if the host IDE has associated a host object with this particular task. /// From bcccfea9d4a57b257188f6cc9a6928e922e898aa Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Wed, 26 Feb 2020 15:03:05 -0800 Subject: [PATCH 002/105] Modified mockengine to support ibuildengine7 Got straightforward unit tests working. Added null check and cached the Semaphore. --- .../Components/RequestBuilder/TaskHost.cs | 13 +++- src/Shared/UnitTests/MockEngine.cs | 46 +++++++++++- src/Tasks.UnitTests/Semaphore_Tests.cs | 70 +++++++++++++++++++ src/Tasks/Microsoft.Build.Tasks.csproj | 2 +- ...maphoreCPUTests.cs => SemaphoreCPUTask.cs} | 13 ++-- 5 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 src/Tasks.UnitTests/Semaphore_Tests.cs rename src/Tasks/{SemaphoreCPUTests.cs => SemaphoreCPUTask.cs} (80%) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 77d4d8d22eb..fd16c77ae57 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -666,11 +666,17 @@ public void LogTelemetry(string eventName, IDictionary propertie #endregion int runningTotal = 0; + Semaphore cpuCount; public int RequestCores(ITask task, int requestedCores) { - Semaphore cpuCount = Semaphore.OpenExisting("cpuCount"); int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; + + if (cpuCount == null) + { + cpuCount = Semaphore.OpenExisting("cpuCount"); + } + // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. for (int i = 0; i < requestedCores; i++) { @@ -689,7 +695,10 @@ public int RequestCores(ITask task, int requestedCores) public void ReleaseCores(ITask task, int coresToRelease) { - Semaphore cpuCount = Semaphore.OpenExisting("cpuCount"); + if (cpuCount == null) + { + cpuCount = Semaphore.OpenExisting("cpuCount"); + } coresToRelease = Math.Min(runningTotal, coresToRelease); diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index 4e8bea7d34c..477f9b1534e 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; +using System.Threading; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; @@ -31,7 +32,7 @@ namespace Microsoft.Build.UnitTests * is somewhat of a no-no for task assemblies. * **************************************************************************/ - internal sealed class MockEngine : IBuildEngine6 + internal sealed class MockEngine : IBuildEngine7 { private readonly object _lockObj = new object(); // Protects _log, _output private readonly ITestOutputHelper _output; @@ -484,5 +485,48 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life _objectCache.TryRemove(key, out object obj); return obj; } + + int runningTotal = 0; + Semaphore cpuCount; + public int RequestCores(ITask task, int requestedCores) + { + if (cpuCount == null) + { + cpuCount = Semaphore.OpenExisting("cpuCount"); + } + + int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; + // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. + for (int i = 0; i < requestedCores; i++) + { + if (cpuCount.WaitOne(0)) + { + runningTotal++; + } + else + { + break; + } + } + + return runningTotal - coresAcquiredBeforeMoreCoresGetAcquired; + } + + public void ReleaseCores(ITask task, int coresToRelease) + { + if(cpuCount == null) + { + cpuCount = Semaphore.OpenExisting("cpuCount"); + } + + coresToRelease = Math.Min(runningTotal, coresToRelease); + + // if we attempt to release 0 cores, Release throws an exception. + if(coresToRelease > 0) + { + cpuCount.Release(coresToRelease); + } + + } } } diff --git a/src/Tasks.UnitTests/Semaphore_Tests.cs b/src/Tasks.UnitTests/Semaphore_Tests.cs new file mode 100644 index 00000000000..3b511003c17 --- /dev/null +++ b/src/Tasks.UnitTests/Semaphore_Tests.cs @@ -0,0 +1,70 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Shouldly; +using Microsoft.Build.UnitTests; +using System.Threading; + +namespace Microsoft.Build.Tasks.UnitTests +{ + public class Semaphore_Tests + { + [Fact] + public void TestRequestingInvalidNumCores() + { + // assume multiproc build of 40 + new Semaphore(40, 40, "cpuCount"); + MockEngine mockEngine = new MockEngine(); + + SemaphoreCPUTask test = new SemaphoreCPUTask(); + test.BuildEngine = mockEngine; + + // 40 - 80 = 0 cores left (claimed 40) + test.BuildEngine7.RequestCores(test, 12312).ShouldBe(40); + test.BuildEngine7.RequestCores(test, 10).ShouldBe(0); + + // 0 + 39 = 39 cores left + test.BuildEngine7.ReleaseCores(test, 39); + + // 39 - 100 = 0 cores left (claimed 39) + test.BuildEngine7.RequestCores(test, 100).ShouldBe(39); + + // 0 + 0 = 0 cores left + test.BuildEngine7.ReleaseCores(test, 0); + test.BuildEngine7.RequestCores(test, 2).ShouldBe(0); + + //0 + 1 = 1 cores left + test.BuildEngine7.ReleaseCores(test, 1); + + // 1 - 2 = 0 cores left (only claimed 1) + test.BuildEngine7.RequestCores(test, 2).ShouldBe(1); + } + + [Fact] + public void TestReleasingInvalidNumCores() + { + // assume multiproc build of 40 + new Semaphore(40, 40, "cpuCount"); + MockEngine mockEngine = new MockEngine(); + + SemaphoreCPUTask test = new SemaphoreCPUTask(); + test.BuildEngine = mockEngine; + + // should still be 40 cores + test.BuildEngine7.ReleaseCores(test, -100); + test.BuildEngine7.RequestCores(test, 41).ShouldBe(40); + + // should be 40 cores to take + test.BuildEngine7.ReleaseCores(test, 50); + test.BuildEngine7.RequestCores(test, 39).ShouldBe(39); + + test.BuildEngine7.RequestCores(test, 2).ShouldBe(1); + } + } +} diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index c89e12c27e3..410f1991f93 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -484,7 +484,7 @@ - + diff --git a/src/Tasks/SemaphoreCPUTests.cs b/src/Tasks/SemaphoreCPUTask.cs similarity index 80% rename from src/Tasks/SemaphoreCPUTests.cs rename to src/Tasks/SemaphoreCPUTask.cs index a0b8030a597..a11027402c1 100644 --- a/src/Tasks/SemaphoreCPUTests.cs +++ b/src/Tasks/SemaphoreCPUTask.cs @@ -1,12 +1,11 @@ -using Microsoft.Build.Utilities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Build.Utilities; namespace Microsoft.Build.Tasks { - class SemaphoreCPUTests : Task + class SemaphoreCPUTask : Task { public override bool Execute() { @@ -26,5 +25,7 @@ public override bool Execute() return !Log.HasLoggedErrors; } + + } } From 73b3466b056c456a6419a2ac9b12ce36f562d635 Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Fri, 28 Feb 2020 14:33:12 -0800 Subject: [PATCH 003/105] Simpler null checks --- .../BackEnd/Components/RequestBuilder/TaskHost.cs | 10 ++-------- src/Shared/UnitTests/MockEngine.cs | 10 ++-------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index fd16c77ae57..7abf6948ee0 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -672,10 +672,7 @@ public int RequestCores(ITask task, int requestedCores) { int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; - if (cpuCount == null) - { - cpuCount = Semaphore.OpenExisting("cpuCount"); - } + cpuCount ??= Semaphore.OpenExisting("cpuCount"); // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. for (int i = 0; i < requestedCores; i++) @@ -695,10 +692,7 @@ public int RequestCores(ITask task, int requestedCores) public void ReleaseCores(ITask task, int coresToRelease) { - if (cpuCount == null) - { - cpuCount = Semaphore.OpenExisting("cpuCount"); - } + cpuCount ??= Semaphore.OpenExisting("cpuCount"); coresToRelease = Math.Min(runningTotal, coresToRelease); diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index 477f9b1534e..07ccf2893f2 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -490,10 +490,7 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life Semaphore cpuCount; public int RequestCores(ITask task, int requestedCores) { - if (cpuCount == null) - { - cpuCount = Semaphore.OpenExisting("cpuCount"); - } + cpuCount ??= Semaphore.OpenExisting("cpuCount"); int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. @@ -514,10 +511,7 @@ public int RequestCores(ITask task, int requestedCores) public void ReleaseCores(ITask task, int coresToRelease) { - if(cpuCount == null) - { - cpuCount = Semaphore.OpenExisting("cpuCount"); - } + cpuCount ??= Semaphore.OpenExisting("cpuCount"); coresToRelease = Math.Min(runningTotal, coresToRelease); From 963a785f77f11c22863034b2d978d2fdaff235f0 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 4 Mar 2020 14:45:05 -0600 Subject: [PATCH 004/105] Clean up API surface --- .../net/Microsoft.Build.Framework.cs | 4 +-- .../netstandard/Microsoft.Build.Framework.cs | 4 +-- .../Components/RequestBuilder/TaskHost.cs | 4 +-- src/Framework/IBuildEngine7.cs | 10 +++---- src/Shared/UnitTests/MockEngine.cs | 4 +-- src/Tasks.UnitTests/Semaphore_Tests.cs | 26 +++++++++---------- src/Tasks/SemaphoreCPUTask.cs | 12 ++++----- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs index 649fa056cbb..691916c7c90 100644 --- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs @@ -205,8 +205,8 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, } public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 { - void ReleaseCores(Microsoft.Build.Framework.ITask task, int releasedCores); - int RequestCores(Microsoft.Build.Framework.ITask task, int requestedCores); + void ReleaseCores(int coresToRelease); + int RequestCores(int requestedCores); } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { diff --git a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs index 38e32b4eaa7..f06e6f1b929 100644 --- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs @@ -205,8 +205,8 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, } public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 { - void ReleaseCores(Microsoft.Build.Framework.ITask task, int releasedCores); - int RequestCores(Microsoft.Build.Framework.ITask task, int requestedCores); + void ReleaseCores(int coresToRelease); + int RequestCores(int requestedCores); } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 47626b64e6c..da942692a31 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -669,7 +669,7 @@ public void LogTelemetry(string eventName, IDictionary propertie int runningTotal = 0; Semaphore cpuCount; - public int RequestCores(ITask task, int requestedCores) + public int RequestCores(int requestedCores) { int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; @@ -691,7 +691,7 @@ public int RequestCores(ITask task, int requestedCores) return runningTotal - coresAcquiredBeforeMoreCoresGetAcquired; } - public void ReleaseCores(ITask task, int coresToRelease) + public void ReleaseCores(int coresToRelease) { cpuCount ??= Semaphore.OpenExisting("cpuCount"); diff --git a/src/Framework/IBuildEngine7.cs b/src/Framework/IBuildEngine7.cs index 1bf8e0b07c8..0d826f2ca14 100644 --- a/src/Framework/IBuildEngine7.cs +++ b/src/Framework/IBuildEngine7.cs @@ -14,15 +14,15 @@ public interface IBuildEngine7 : IBuildEngine6 /// /// If a task launches multiple parallel processes, it should ask how many cores it can use. /// - /// The number of cores a task can potentially use - /// The number of cores a task is allowed to use - int RequestCores(ITask task, int requestedCores); + /// The number of cores a task can potentially use. + /// The number of cores a task is allowed to use. + int RequestCores(int requestedCores); /// /// A task should notify the build manager when all or some of the requested cores are not used anymore. /// When task is finished, the cores it requested are automatically released. /// - /// - void ReleaseCores(ITask task, int releasedCores); + /// Number of cores no longer in use. + void ReleaseCores(int coresToRelease); } } diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index 07ccf2893f2..2cf1154d834 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -488,7 +488,7 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life int runningTotal = 0; Semaphore cpuCount; - public int RequestCores(ITask task, int requestedCores) + public int RequestCores(int requestedCores) { cpuCount ??= Semaphore.OpenExisting("cpuCount"); @@ -509,7 +509,7 @@ public int RequestCores(ITask task, int requestedCores) return runningTotal - coresAcquiredBeforeMoreCoresGetAcquired; } - public void ReleaseCores(ITask task, int coresToRelease) + public void ReleaseCores(int coresToRelease) { cpuCount ??= Semaphore.OpenExisting("cpuCount"); diff --git a/src/Tasks.UnitTests/Semaphore_Tests.cs b/src/Tasks.UnitTests/Semaphore_Tests.cs index 3b511003c17..7c0f858871a 100644 --- a/src/Tasks.UnitTests/Semaphore_Tests.cs +++ b/src/Tasks.UnitTests/Semaphore_Tests.cs @@ -26,24 +26,24 @@ public void TestRequestingInvalidNumCores() test.BuildEngine = mockEngine; // 40 - 80 = 0 cores left (claimed 40) - test.BuildEngine7.RequestCores(test, 12312).ShouldBe(40); - test.BuildEngine7.RequestCores(test, 10).ShouldBe(0); + test.BuildEngine7.RequestCores(12312).ShouldBe(40); + test.BuildEngine7.RequestCores(10).ShouldBe(0); // 0 + 39 = 39 cores left - test.BuildEngine7.ReleaseCores(test, 39); + test.BuildEngine7.ReleaseCores(39); // 39 - 100 = 0 cores left (claimed 39) - test.BuildEngine7.RequestCores(test, 100).ShouldBe(39); + test.BuildEngine7.RequestCores(100).ShouldBe(39); // 0 + 0 = 0 cores left - test.BuildEngine7.ReleaseCores(test, 0); - test.BuildEngine7.RequestCores(test, 2).ShouldBe(0); + test.BuildEngine7.ReleaseCores(0); + test.BuildEngine7.RequestCores(2).ShouldBe(0); //0 + 1 = 1 cores left - test.BuildEngine7.ReleaseCores(test, 1); + test.BuildEngine7.ReleaseCores(1); // 1 - 2 = 0 cores left (only claimed 1) - test.BuildEngine7.RequestCores(test, 2).ShouldBe(1); + test.BuildEngine7.RequestCores(2).ShouldBe(1); } [Fact] @@ -57,14 +57,14 @@ public void TestReleasingInvalidNumCores() test.BuildEngine = mockEngine; // should still be 40 cores - test.BuildEngine7.ReleaseCores(test, -100); - test.BuildEngine7.RequestCores(test, 41).ShouldBe(40); + test.BuildEngine7.ReleaseCores(-100); + test.BuildEngine7.RequestCores(41).ShouldBe(40); // should be 40 cores to take - test.BuildEngine7.ReleaseCores(test, 50); - test.BuildEngine7.RequestCores(test, 39).ShouldBe(39); + test.BuildEngine7.ReleaseCores(50); + test.BuildEngine7.RequestCores(39).ShouldBe(39); - test.BuildEngine7.RequestCores(test, 2).ShouldBe(1); + test.BuildEngine7.RequestCores(2).ShouldBe(1); } } } diff --git a/src/Tasks/SemaphoreCPUTask.cs b/src/Tasks/SemaphoreCPUTask.cs index a11027402c1..b09bc4f5496 100644 --- a/src/Tasks/SemaphoreCPUTask.cs +++ b/src/Tasks/SemaphoreCPUTask.cs @@ -9,19 +9,19 @@ class SemaphoreCPUTask : Task { public override bool Execute() { - Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(this, 3123890)} cores", Framework.MessageImportance.High); + Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(3123890)} cores", Framework.MessageImportance.High); - BuildEngine7.ReleaseCores(this, 50); + BuildEngine7.ReleaseCores(50); Log.LogMessageFromText("Released some number of cores", Framework.MessageImportance.High); - Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(this, 10)} cores", Framework.MessageImportance.High); + Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(10)} cores", Framework.MessageImportance.High); - Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(this, 30)} cores", Framework.MessageImportance.High); + Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(30)} cores", Framework.MessageImportance.High); - BuildEngine7.ReleaseCores(this, 2); + BuildEngine7.ReleaseCores(2); Log.LogMessageFromText("Released some number of cores", Framework.MessageImportance.High); - Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(this, 12)} cores", Framework.MessageImportance.High); + Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(12)} cores", Framework.MessageImportance.High); return !Log.HasLoggedErrors; } From e1a30e6f405b7e2667637dbb7435e80dde501233 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Mar 2020 14:51:14 -0600 Subject: [PATCH 005/105] Tasks type name --- src/Tasks/Microsoft.Common.tasks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tasks/Microsoft.Common.tasks b/src/Tasks/Microsoft.Common.tasks index 68937800130..6d036f5c03b 100644 --- a/src/Tasks/Microsoft.Common.tasks +++ b/src/Tasks/Microsoft.Common.tasks @@ -134,7 +134,7 @@ - + From 055116a4ad000093082dce23a4c4298d3b616d9e Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Mar 2020 14:51:24 -0600 Subject: [PATCH 006/105] simple project --- test-project.proj | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 test-project.proj diff --git a/test-project.proj b/test-project.proj new file mode 100644 index 00000000000..745430c88b4 --- /dev/null +++ b/test-project.proj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 10f6eedcdbb6af2b54abbe9296aac7caedf367ef Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Mar 2020 14:57:55 -0600 Subject: [PATCH 007/105] Simplify if-appdomain in NodeConfiguration --- src/Build/BackEnd/Node/NodeConfiguration.cs | 27 +++------------------ 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/Build/BackEnd/Node/NodeConfiguration.cs b/src/Build/BackEnd/Node/NodeConfiguration.cs index 5cb25db468c..7052f8c4014 100644 --- a/src/Build/BackEnd/Node/NodeConfiguration.cs +++ b/src/Build/BackEnd/Node/NodeConfiguration.cs @@ -40,7 +40,6 @@ internal class NodeConfiguration : INodePacket /// private LoggingNodeConfiguration _loggingNodeConfiguration; -#if FEATURE_APPDOMAIN /// /// Constructor /// @@ -54,38 +53,20 @@ public NodeConfiguration int nodeId, BuildParameters buildParameters, LoggerDescription[] forwardingLoggers, +#if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, +#endif LoggingNodeConfiguration loggingNodeConfiguration ) { _nodeId = nodeId; _buildParameters = buildParameters; _forwardingLoggers = forwardingLoggers; +#if FEATURE_APPDOMAIN _appDomainSetup = appDomainSetup; +#endif _loggingNodeConfiguration = loggingNodeConfiguration; } -#else - /// - /// Constructor - /// - /// The node id. - /// The build parameters - /// The forwarding loggers. - /// The logging configuration for the node. - public NodeConfiguration - ( - int nodeId, - BuildParameters buildParameters, - LoggerDescription[] forwardingLoggers, - LoggingNodeConfiguration loggingNodeConfiguration - ) - { - _nodeId = nodeId; - _buildParameters = buildParameters; - _forwardingLoggers = forwardingLoggers; - _loggingNodeConfiguration = loggingNodeConfiguration; - } -#endif /// /// Private constructor for deserialization From aa3f3855d5ec2a4bff78e3981431eb539b73cdd1 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 6 Mar 2020 16:17:31 -0600 Subject: [PATCH 008/105] Checkpoint --- .../BuildComponentFactoryCollection.cs | 3 + .../BackEnd/Components/IBuildComponentHost.cs | 5 ++ .../Components/RequestBuilder/TaskHost.cs | 30 +++---- .../ResourceManager/ResourceManagerService.cs | 81 +++++++++++++++++++ src/Build/Microsoft.Build.csproj | 1 + src/MSBuild/XMake.cs | 1 - test-project.proj | 2 + 7 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs diff --git a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs index 05ea23f5425..6c85709a550 100644 --- a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs +++ b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Microsoft.Build.BackEnd.Components.Caching; +using Microsoft.Build.BackEnd.Components.ResourceManager; using Microsoft.Build.BackEnd.SdkResolution; using Microsoft.Build.Shared; @@ -78,6 +79,8 @@ public void RegisterDefaultFactories() // SDK resolution _componentEntriesByType[BuildComponentType.SdkResolverService] = new BuildComponentEntry(BuildComponentType.SdkResolverService, MainNodeSdkResolverService.CreateComponent, CreationPattern.Singleton); + + _componentEntriesByType[BuildComponentType.TaskResourceManager] = new BuildComponentEntry(BuildComponentType.TaskResourceManager, ResourceManagerService.CreateComponent, CreationPattern.Singleton); } /// diff --git a/src/Build/BackEnd/Components/IBuildComponentHost.cs b/src/Build/BackEnd/Components/IBuildComponentHost.cs index 6e18d9ec4ef..718e83ae794 100644 --- a/src/Build/BackEnd/Components/IBuildComponentHost.cs +++ b/src/Build/BackEnd/Components/IBuildComponentHost.cs @@ -128,6 +128,11 @@ internal enum BuildComponentType /// The SDK resolution service. /// SdkResolverService, + + /// + /// Resource manager for tasks to use via . + /// + TaskResourceManager, } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index da942692a31..2d001fa83e9 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -23,6 +23,7 @@ using Microsoft.Build.BackEnd.Components.Caching; using System.Reflection; using Microsoft.Build.Eventing; +using Microsoft.Build.BackEnd.Components.ResourceManager; namespace Microsoft.Build.BackEnd { @@ -667,37 +668,32 @@ public void LogTelemetry(string eventName, IDictionary propertie #endregion int runningTotal = 0; - Semaphore cpuCount; public int RequestCores(int requestedCores) { + var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; + int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; - cpuCount ??= Semaphore.OpenExisting("cpuCount"); + var coresAcquired = rms.RequestCores(requestedCores); - // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. - for (int i = 0; i < requestedCores; i++) - { - if(cpuCount.WaitOne(0)) - { - runningTotal++; - } - else - { - break; - } - } + runningTotal += coresAcquired; - return runningTotal - coresAcquiredBeforeMoreCoresGetAcquired; + return coresAcquired; } public void ReleaseCores(int coresToRelease) { - cpuCount ??= Semaphore.OpenExisting("cpuCount"); + var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; coresToRelease = Math.Min(runningTotal, coresToRelease); - cpuCount.Release(coresToRelease); + if (coresToRelease >= 1) + { + runningTotal -= coresToRelease; + + rms.ReleaseCores(coresToRelease); + } } /// diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs new file mode 100644 index 00000000000..ad65923dcf1 --- /dev/null +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Build.Shared; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace Microsoft.Build.BackEnd.Components.ResourceManager +{ + class ResourceManagerService : IBuildComponent + { + Semaphore? s = null; + + internal static IBuildComponent CreateComponent(BuildComponentType type) + { + ErrorUtilities.VerifyThrow(type == BuildComponentType.TaskResourceManager, "Cannot create components of type {0}", type); + + return new ResourceManagerService(); + } + + public void InitializeComponent(IBuildComponentHost host) + { + const string SemaphoreName = "cpuCount"; // TODO + + int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability + + s = new Semaphore(resourceCount, resourceCount, SemaphoreName); // TODO: SemaphoreSecurity? + } + + public void ShutdownComponent() + { + s?.Dispose(); + s = null; + } + + public int RequestCores(int requestedCores) + { + if (s is null) + { + // TODO: ErrorUtilities should be annotated so this can just be `ErrorUtilities.VerifyThrow` + // https://github.com/microsoft/msbuild/issues/5163 + throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); + } + + int i = 0; + + // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. + for (i = 0; i < requestedCores; i++) + { + if (!s.WaitOne(0)) + { + return i; + } + + Console.WriteLine("Got a core"); + } + + return i; + } + + public void ReleaseCores(int coresToRelease) + { + if (s is null) + { + // TODO: ErrorUtilities should be annotated so this can just be `ErrorUtilities.VerifyThrow` + // https://github.com/microsoft/msbuild/issues/5163 + throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); + } + + ErrorUtilities.VerifyThrow(coresToRelease > 0, "Tried to release {0} cores", coresToRelease); + + s.Release(coresToRelease); + } + } +} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index aa55562d5a5..5329a7386e3 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -157,6 +157,7 @@ + diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 19ad5bd1ded..7b96f13f170 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -1176,7 +1176,6 @@ string outputResultsCache #if MSBUILDENABLEVSPROFILING DataCollection.CommentMarkProfile(8800, "Pending Build Request from MSBuild.exe"); #endif - new Semaphore(cpuCount, cpuCount, "cpuCount"); BuildResultCode? result = null; diff --git a/test-project.proj b/test-project.proj index 745430c88b4..194b8744343 100644 --- a/test-project.proj +++ b/test-project.proj @@ -1,5 +1,7 @@ + + \ No newline at end of file From 57a5c936ab1e2e3dc05b189858280905ccce9931 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 6 Mar 2020 17:20:19 -0600 Subject: [PATCH 009/105] Remove FEATURE_VARIOUS_EXCEPTIONS --- eng/Packages.props | 3 ++- src/Build.UnitTests/BackEnd/BuildResult_Tests.cs | 2 -- src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs | 2 -- src/Build.UnitTests/BackEnd/LoggingService_Tests.cs | 8 -------- .../Components/RequestBuilder/RequestBuilder.cs | 2 -- .../BackEnd/Components/RequestBuilder/TaskBuilder.cs | 2 -- src/Build/BackEnd/Node/InProcNode.cs | 2 -- src/Directory.BeforeCommon.targets | 1 - src/Framework/Microsoft.Build.Framework.csproj | 1 + src/MSBuild/OutOfProcTaskHostNode.cs | 2 -- src/Shared/ExceptionHandling.cs | 12 ------------ src/Tasks/AppConfig/AppConfigException.cs | 4 ---- src/Tasks/Microsoft.Build.Tasks.csproj | 1 + src/Utilities/Microsoft.Build.Utilities.csproj | 1 + 14 files changed, 5 insertions(+), 38 deletions(-) diff --git a/eng/Packages.props b/eng/Packages.props index 80e14eb1d86..baec54fee41 100644 --- a/eng/Packages.props +++ b/eng/Packages.props @@ -37,7 +37,8 @@ - + + diff --git a/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs b/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs index fb14064c4f4..af9bf647c5d 100644 --- a/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs @@ -83,12 +83,10 @@ public void TestExceptionGood() BuildRequest request = CreateNewBuildRequest(1, new string[0]); BuildResult result = new BuildResult(request); Assert.Null(result.Exception); -#if FEATURE_VARIOUS_EXCEPTIONS AccessViolationException e = new AccessViolationException(); result = new BuildResult(request, e); Assert.Equal(e, result.Exception); -#endif } [Fact] diff --git a/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs b/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs index f19fe33cb1d..4517bb7c85a 100644 --- a/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs +++ b/src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs @@ -91,9 +91,7 @@ public void LoggerExceptionInEventHandler() List exceptionList = new List(); exceptionList.Add(new LoggerException()); exceptionList.Add(new ArgumentException()); -#if FEATURE_VARIOUS_EXCEPTIONS exceptionList.Add(new StackOverflowException()); -#endif foreach (Exception exception in exceptionList) { diff --git a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs index f37b423f1bc..2162e074edb 100644 --- a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs @@ -152,13 +152,11 @@ public void ShutDownComponentExceptionsInForwardingLogger() VerifyShutdownExceptions(null, className, exceptionType); Assert.Equal(LoggingServiceState.Shutdown, _initializedService.ServiceState); -#if FEATURE_VARIOUS_EXCEPTIONS // Cause a StackOverflow exception in the shutdown of the logger // this kind of exception should not be caught className = "Microsoft.Build.UnitTests.Logging.LoggingService_Tests+ShutdownStackoverflowExceptionFL"; exceptionType = typeof(StackOverflowException); VerifyShutdownExceptions(null, className, exceptionType); -#endif Assert.Equal(LoggingServiceState.Shutdown, _initializedService.ServiceState); } @@ -176,10 +174,8 @@ public void ShutDownComponentExceptionsInLogger() logger = new LoggerThrowException(true, false, new Exception("boo")); VerifyShutdownExceptions(logger, null, typeof(InternalLoggerException)); -#if FEATURE_VARIOUS_EXCEPTIONS logger = new LoggerThrowException(true, false, new StackOverflowException()); VerifyShutdownExceptions(logger, null, typeof(StackOverflowException)); -#endif Assert.Equal(LoggingServiceState.Shutdown, _initializedService.ServiceState); } @@ -257,7 +253,6 @@ public void GeneralExceptionInInitialize() ); } -#if FEATURE_VARIOUS_EXCEPTIONS /// /// Verify a critical exception is not wrapped /// @@ -271,7 +266,6 @@ public void ILoggerExceptionInInitialize() } ); } -#endif /// /// Register an good Logger and verify it was registered. @@ -1170,7 +1164,6 @@ public ShutdownGeneralExceptionFL() } } -#if FEATURE_VARIOUS_EXCEPTIONS /// /// Forwarding logger which will throw a StackOverflowException /// in the shutdown method. This is to test the shutdown exception handling @@ -1186,7 +1179,6 @@ public ShutdownStackoverflowExceptionFL() { } } -#endif /// /// Logger which can throw a defined exception in the initialize or shutdown methods diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index f82ff724cac..4cca645d906 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -690,14 +690,12 @@ private async Task RequestThreadProc(bool setThreadParameters) await BuildAndReport(); MSBuildEventSource.Log.RequestThreadProcStop(); } -#if FEATURE_VARIOUS_EXCEPTIONS catch (ThreadAbortException) { // Do nothing. This will happen when the thread is forcibly terminated because we are shutting down, for example // when the unit test framework terminates. throw; } -#endif catch (Exception e) { // Dump all engine exceptions to a temp file diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index 86731a89db0..9324b8d20b1 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -864,7 +864,6 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta // Rethrow wrapped in order to avoid losing the callstack throw new InternalLoggerException(taskException.Message, taskException, ex.BuildEventArgs, ex.ErrorCode, ex.HelpKeyword, ex.InitializationException); } -#if FEATURE_VARIOUS_EXCEPTIONS else if (type == typeof(ThreadAbortException)) { Thread.ResetAbort(); @@ -874,7 +873,6 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta // Stack will be lost throw taskException; } -#endif else if (type == typeof(BuildAbortedException)) { _continueOnError = ContinueOnError.ErrorAndStop; diff --git a/src/Build/BackEnd/Node/InProcNode.cs b/src/Build/BackEnd/Node/InProcNode.cs index f86ecad6b7c..360bf3d3193 100644 --- a/src/Build/BackEnd/Node/InProcNode.cs +++ b/src/Build/BackEnd/Node/InProcNode.cs @@ -163,14 +163,12 @@ public NodeEngineShutdownReason Run(out Exception shutdownException) } } } -#if FEATURE_VARIOUS_EXCEPTIONS catch (ThreadAbortException) { // Do nothing. This will happen when the thread is forcibly terminated because we are shutting down, for example // when the unit test framework terminates. throw; } -#endif catch (Exception e) { // Dump all engine exceptions to a temp file diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets index 57a2344e364..8e63a94f93d 100644 --- a/src/Directory.BeforeCommon.targets +++ b/src/Directory.BeforeCommon.targets @@ -91,7 +91,6 @@ $(DefineConstants);FEATURE_TYPE_GETINTERFACE $(DefineConstants);FEATURE_USERINTERACTIVE $(DefineConstants);FEATURE_USERDOMAINNAME - $(DefineConstants);FEATURE_VARIOUS_EXCEPTIONS $(DefineConstants);FEATURE_XAML_TYPES $(DefineConstants);FEATURE_XAMLTASKFACTORY true diff --git a/src/Framework/Microsoft.Build.Framework.csproj b/src/Framework/Microsoft.Build.Framework.csproj index 1327e0ad814..4481e255400 100644 --- a/src/Framework/Microsoft.Build.Framework.csproj +++ b/src/Framework/Microsoft.Build.Framework.csproj @@ -14,6 +14,7 @@ + diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index af10ead716a..875b3749c4a 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -837,14 +837,12 @@ private void RunTask(object state) } catch (Exception e) { -#if FEATURE_VARIOUS_EXCEPTIONS if (e is ThreadAbortException) { // This thread was aborted as part of Cancellation, we will return a failure task result taskResult = new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure); } else -#endif if (ExceptionHandling.IsCriticalException(e)) { throw; diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs index 0535ec7ad29..ae0dac9bfe3 100644 --- a/src/Shared/ExceptionHandling.cs +++ b/src/Shared/ExceptionHandling.cs @@ -17,10 +17,8 @@ namespace Microsoft.Build.AppxPackage.Shared using System.Threading; using System.Xml; using Microsoft.Build.Shared.FileSystem; -#if FEATURE_VARIOUS_EXCEPTIONS using System.Xml.Schema; using System.Runtime.Serialization; -#endif namespace Microsoft.Build.Shared #endif @@ -70,12 +68,10 @@ private static string GetDebugDumpPath() internal static bool IsCriticalException(Exception e) { if (e is OutOfMemoryException -#if FEATURE_VARIOUS_EXCEPTIONS || e is StackOverflowException || e is ThreadAbortException || e is ThreadInterruptedException || e is AccessViolationException -#endif #if !BUILDINGAPPXTASKS || e is InternalErrorException #endif @@ -144,10 +140,8 @@ internal static bool IsIoRelatedException(Exception e) internal static bool IsXmlException(Exception e) { return e is XmlException -#if FEATURE_VARIOUS_EXCEPTIONS || e is XmlSyntaxException || e is XmlSchemaException -#endif || e is UriFormatException; // XmlTextReader for example uses this under the covers } @@ -168,14 +162,12 @@ internal static LineAndColumn GetXmlLineAndColumn(Exception e) } else { -#if FEATURE_VARIOUS_EXCEPTIONS var schemaException = e as XmlSchemaException; if (schemaException != null) { line = schemaException.LineNumber; column = schemaException.LinePosition; } -#endif } return new LineAndColumn @@ -227,12 +219,10 @@ internal static bool NotExpectedReflectionException(Exception e) || e is TargetParameterCountException // thrown when the number of parameters for an invocation does not match the number expected || e is InvalidCastException || e is AmbiguousMatchException // thrown when binding to a member results in more than one member matching the binding criteria -#if FEATURE_VARIOUS_EXCEPTIONS || e is CustomAttributeFormatException // thrown if a custom attribute on a data type is formatted incorrectly || e is InvalidFilterCriteriaException // thrown in FindMembers when the filter criteria is not valid for the type of filter you are using || e is TargetException // thrown when an attempt is made to invoke a non-static method on a null object. This may occur because the caller does not // have access to the member, or because the target does not define the member, and so on. -#endif || e is MissingFieldException // thrown when code in a dependent assembly attempts to access a missing field in an assembly that was modified. || !NotExpectedException(e) // Reflection can throw IO exceptions if the assembly cannot be opened @@ -253,9 +243,7 @@ internal static bool NotExpectedSerializationException(Exception e) { if ( -#if FEATURE_VARIOUS_EXCEPTIONS e is SerializationException || -#endif !NotExpectedReflectionException(e) ) { diff --git a/src/Tasks/AppConfig/AppConfigException.cs b/src/Tasks/AppConfig/AppConfigException.cs index 6d69ff98c6c..5d6b76ebf8c 100644 --- a/src/Tasks/AppConfig/AppConfigException.cs +++ b/src/Tasks/AppConfig/AppConfigException.cs @@ -11,11 +11,7 @@ namespace Microsoft.Build.Tasks /// [Serializable] internal class AppConfigException : -#if FEATURE_VARIOUS_EXCEPTIONS System.ApplicationException -#else - Exception -#endif { /// /// The name of the app.config file. diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index 6e2421f9294..1877f25594e 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -993,6 +993,7 @@ + diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index 031130a2197..e77bd5df12b 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -30,6 +30,7 @@ + From 92291220c31fca9085cd97bee35257a453403e99 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Mon, 9 Mar 2020 10:54:51 -0500 Subject: [PATCH 010/105] Checkpoint: works on full only, doesn't properly block when all resources consumed but want to start a task --- .../BackEnd/Components/RequestBuilder/TaskBuilder.cs | 4 ++-- .../BackEnd/Components/RequestBuilder/TaskHost.cs | 12 ++++++++++++ .../ResourceManager/ResourceManagerService.cs | 2 -- src/Tasks.UnitTests/Semaphore_Tests.cs | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index 9324b8d20b1..1851228a294 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -353,12 +353,12 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo { _taskExecutionHost.CleanupForTask(); -#if FEATURE_APPDOMAIN if (taskHost != null) { +#if FEATURE_APPDOMAIN taskHost.MarkAsInactive(); - } #endif + } // Now all task batches are done, apply all item adds to the outer // target batch; we do this even if the task wasn't found (in that case, diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 2d001fa83e9..fe892f02949 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -126,6 +126,9 @@ public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, Elemen _continueOnError = false; _activeProxy = true; _callbackMonitor = new Object(); + + // Ensure that we have at least one core to run this task + RequestCores(1); } /// @@ -696,6 +699,13 @@ public void ReleaseCores(int coresToRelease) } } + internal void ReleaseAllCores() + { + ReleaseCores(runningTotal); + + runningTotal = 0; + } + /// /// Called by the internal MSBuild task. /// Does not take the lock because it is called by another request builder thread. @@ -814,6 +824,8 @@ internal void MarkAsInactive() VerifyActiveProxy(); _activeProxy = false; + ReleaseAllCores(); + // Since the task has a pointer to this class it may store it in a static field. Null out // internal data so the leak of this object doesn't lead to a major memory leak. _host = null; diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index ad65923dcf1..28db29bc319 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -57,8 +57,6 @@ public int RequestCores(int requestedCores) { return i; } - - Console.WriteLine("Got a core"); } return i; diff --git a/src/Tasks.UnitTests/Semaphore_Tests.cs b/src/Tasks.UnitTests/Semaphore_Tests.cs index 7c0f858871a..57e64f13c66 100644 --- a/src/Tasks.UnitTests/Semaphore_Tests.cs +++ b/src/Tasks.UnitTests/Semaphore_Tests.cs @@ -46,7 +46,7 @@ public void TestRequestingInvalidNumCores() test.BuildEngine7.RequestCores(2).ShouldBe(1); } - [Fact] + [Fact(Skip = "TODO: test harness to tweak number of assignable cores")] public void TestReleasingInvalidNumCores() { // assume multiproc build of 40 From b9198344329857dc72be98c047e0828165343d40 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Mon, 9 Mar 2020 11:14:39 -0500 Subject: [PATCH 011/105] Make TaskHost.MarkAsInactive work on Core --- src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs | 7 +------ src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 4 +++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index 1851228a294..bcb11463ae9 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -353,12 +353,7 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo { _taskExecutionHost.CleanupForTask(); - if (taskHost != null) - { -#if FEATURE_APPDOMAIN - taskHost.MarkAsInactive(); -#endif - } + taskHost?.MarkAsInactive(); // Now all task batches are done, apply all item adds to the outer // target batch; we do this even if the task wasn't found (in that case, diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index fe892f02949..e91106323fa 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -812,6 +812,7 @@ public override object InitializeLifetimeService() return lease; } } +#endif /// /// Indicates to the TaskHost that it is no longer needed. @@ -835,6 +836,7 @@ internal void MarkAsInactive() _taskLoggingContext = null; _targetBuilderCallback = null; +#if FEATURE_APPDOMAIN // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done) // this will be null if the engine proxy was never sent across an AppDomain boundary. if (_sponsor != null) @@ -849,9 +851,9 @@ internal void MarkAsInactive() _sponsor.Close(); _sponsor = null; } +#endif } } -#endif /// /// Determine if the event is serializable. If we are running with multiple nodes we need to make sure the logging events are serializable. If not From 01953aa70297b872ffd63cb4de881906fdcf58c0 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 12 Mar 2020 11:44:56 -0500 Subject: [PATCH 012/105] Tweak test to show cross-process handling --- src/Tasks/SemaphoreCPUTask.cs | 40 ++++++++++++++++++++++------------- test-project.proj | 9 +++++++- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/Tasks/SemaphoreCPUTask.cs b/src/Tasks/SemaphoreCPUTask.cs index b09bc4f5496..89f00bd0a0d 100644 --- a/src/Tasks/SemaphoreCPUTask.cs +++ b/src/Tasks/SemaphoreCPUTask.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Build.Utilities; +using System; +using System.Threading; namespace Microsoft.Build.Tasks { @@ -9,23 +11,31 @@ class SemaphoreCPUTask : Task { public override bool Execute() { - Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(3123890)} cores", Framework.MessageImportance.High); - - BuildEngine7.ReleaseCores(50); - Log.LogMessageFromText("Released some number of cores", Framework.MessageImportance.High); - - Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(10)} cores", Framework.MessageImportance.High); - - Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(30)} cores", Framework.MessageImportance.High); - - BuildEngine7.ReleaseCores(2); - Log.LogMessageFromText("Released some number of cores", Framework.MessageImportance.High); - - Log.LogMessageFromText($"Got {BuildEngine7.RequestCores(12)} cores", Framework.MessageImportance.High); + int initial = BuildEngine7.RequestCores(3123890); + Log.LogMessageFromText($"Got {initial} cores from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + + if (initial > 0) + { + while (initial > 0) + { + Thread.Sleep(TimeSpan.FromSeconds(1)); + BuildEngine7.ReleaseCores(1); + initial--; + Log.LogMessageFromText($"Released 1 core from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + } + + return !Log.HasLoggedErrors; + } + + for (int i = 0; i < 20; i++) + { + int v = BuildEngine7.RequestCores(9999); + Log.LogMessageFromText($"Got {v} cores from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + BuildEngine7.ReleaseCores(v + 20); + Thread.Sleep(TimeSpan.FromSeconds(0.9)); + } return !Log.HasLoggedErrors; } - - } } diff --git a/test-project.proj b/test-project.proj index 194b8744343..3f46eb56486 100644 --- a/test-project.proj +++ b/test-project.proj @@ -1,7 +1,14 @@ + +

+

+ + + + - + \ No newline at end of file From 1183e877562bea3f4a7496d1e9a5f3fe3aaeb213 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 12 Mar 2020 14:21:57 -0500 Subject: [PATCH 013/105] Introduce RequireCores --- .../Components/RequestBuilder/TaskHost.cs | 11 ++++++++++- .../ResourceManager/ResourceManagerService.cs | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index e91106323fa..0f93e233f57 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -128,7 +128,7 @@ public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, Elemen _callbackMonitor = new Object(); // Ensure that we have at least one core to run this task - RequestCores(1); + RequireCores(1); } ///

@@ -685,6 +685,15 @@ public int RequestCores(int requestedCores) return coresAcquired; } + private void RequireCores(int requestedCores) + { + var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; + + rms.RequireCores(requestedCores); + + runningTotal += 1; // default reservation + } + public void ReleaseCores(int coresToRelease) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 28db29bc319..5a4b6bf5118 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -75,5 +75,21 @@ public void ReleaseCores(int coresToRelease) s.Release(coresToRelease); } + + internal void RequireCores(int requestedCores) + { + if (s is null) + { + // TODO: ErrorUtilities should be annotated so this can just be `ErrorUtilities.VerifyThrow` + // https://github.com/microsoft/msbuild/issues/5163 + throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); + } + + if (!s.WaitOne()) + { + ErrorUtilities.ThrowInternalError("Couldn't get a core to run a task even with infinite timeout"); + + } + } } } From 0437c325f622cd5da215817ed86fe2d9356a890e Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 12 Mar 2020 16:57:22 -0500 Subject: [PATCH 014/105] WIP --- .../BackEnd/BuildManager/BuildManager.cs | 1 + .../ResourceManager/ResourceManagerService.cs | 7 ++++++ .../ResourceManagerServiceFactory.cs | 25 +++++++++++++++++++ src/Build/BackEnd/Node/NodeConfiguration.cs | 21 ++++++++++++++-- src/Build/BackEnd/Node/OutOfProcNode.cs | 6 +++++ src/Build/Microsoft.Build.csproj | 1 + 6 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/Build/BackEnd/Components/ResourceManager/ResourceManagerServiceFactory.cs diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index b2157cf4e07..cf08dc532c5 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2154,6 +2154,7 @@ private NodeConfiguration GetNodeConfiguration() , AppDomain.CurrentDomain.SetupInformation #endif , new LoggingNodeConfiguration(loggingService.IncludeEvaluationMetaprojects, loggingService.IncludeEvaluationProfile, loggingService.IncludeTaskInputs) + , "MSBuildCpuCount" ); } diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 5a4b6bf5118..cc83d182199 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -17,6 +17,13 @@ class ResourceManagerService : IBuildComponent { Semaphore? s = null; + private string _semaphoreName; + + public ResourceManagerService(string semaphoreName) + { + _semaphoreName = semaphoreName; + } + internal static IBuildComponent CreateComponent(BuildComponentType type) { ErrorUtilities.VerifyThrow(type == BuildComponentType.TaskResourceManager, "Cannot create components of type {0}", type); diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerServiceFactory.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerServiceFactory.cs new file mode 100644 index 00000000000..3b3dc53a7c8 --- /dev/null +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerServiceFactory.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Build.Shared; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace Microsoft.Build.BackEnd.Components.ResourceManager +{ + class ResourceManagerServiceFactory + { + public IBuildComponent CreateInstance(BuildComponentType type) + { + // Create the instance of OutOfProcNodeSdkResolverService and pass parameters to the constructor. + return new ResourceManagerService(); + } + + } +} diff --git a/src/Build/BackEnd/Node/NodeConfiguration.cs b/src/Build/BackEnd/Node/NodeConfiguration.cs index 7052f8c4014..ab0db7c232f 100644 --- a/src/Build/BackEnd/Node/NodeConfiguration.cs +++ b/src/Build/BackEnd/Node/NodeConfiguration.cs @@ -40,6 +40,8 @@ internal class NodeConfiguration : INodePacket /// private LoggingNodeConfiguration _loggingNodeConfiguration; + private string _resourceManagerSemaphoreName; + /// /// Constructor /// @@ -56,7 +58,8 @@ public NodeConfiguration #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif - LoggingNodeConfiguration loggingNodeConfiguration + LoggingNodeConfiguration loggingNodeConfiguration, + string resourceManagerSemaphoreName ) { _nodeId = nodeId; @@ -66,6 +69,7 @@ LoggingNodeConfiguration loggingNodeConfiguration _appDomainSetup = appDomainSetup; #endif _loggingNodeConfiguration = loggingNodeConfiguration; + _resourceManagerSemaphoreName = resourceManagerSemaphoreName; } /// @@ -131,7 +135,18 @@ public LoggingNodeConfiguration LoggingNodeConfiguration { return _loggingNodeConfiguration; } } -#region INodePacket Members + /// + /// Name of the semaphore that communicates resource use between nodes. + /// + public string ResourceManagerSemaphoreName + { + [DebuggerStepThrough] + get + { return _resourceManagerSemaphoreName; } + + } + + #region INodePacket Members /// /// Retrieves the packet type. @@ -160,6 +175,7 @@ public void Translate(ITranslator translator) translator.TranslateDotNet(ref _appDomainSetup); #endif translator.Translate(ref _loggingNodeConfiguration); + translator.Translate(ref _resourceManagerSemaphoreName); } /// @@ -183,6 +199,7 @@ internal NodeConfiguration Clone() , _appDomainSetup #endif , _loggingNodeConfiguration + , _resourceManagerSemaphoreName ); } } diff --git a/src/Build/BackEnd/Node/OutOfProcNode.cs b/src/Build/BackEnd/Node/OutOfProcNode.cs index be436db5431..073e1e3c842 100644 --- a/src/Build/BackEnd/Node/OutOfProcNode.cs +++ b/src/Build/BackEnd/Node/OutOfProcNode.cs @@ -18,6 +18,7 @@ using Microsoft.Build.BackEnd.Components.Caching; using Microsoft.Build.BackEnd.SdkResolution; using SdkResult = Microsoft.Build.BackEnd.SdkResolution.SdkResult; +using Microsoft.Build.BackEnd.Components.ResourceManager; namespace Microsoft.Build.Execution { @@ -639,6 +640,11 @@ private void HandleNodeConfiguration(NodeConfiguration configuration) // Grab the system parameters. _buildParameters = configuration.BuildParameters; + //configuration.ResourceManagerSemaphoreName + + ((IBuildComponentHost)this).RegisterFactory(BuildComponentType.TaskResourceManager, new ResourceManagerServiceFactory(configuration.ResourceManagerSemaphoreName).CreateInstance); + + _buildParameters.ProjectRootElementCache = s_projectRootElementCacheBase; // Snapshot the current environment diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 5329a7386e3..eccd3580691 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -157,6 +157,7 @@ + From 1bf3017575c03d18352f6c757d772997913f044a Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 12 Mar 2020 16:57:25 -0500 Subject: [PATCH 015/105] Revert "WIP" This reverts commit f60c66a605c667d4190405af63050f972cca8b80. --- .../BackEnd/BuildManager/BuildManager.cs | 1 - .../ResourceManager/ResourceManagerService.cs | 7 ------ .../ResourceManagerServiceFactory.cs | 25 ------------------- src/Build/BackEnd/Node/NodeConfiguration.cs | 21 ++-------------- src/Build/BackEnd/Node/OutOfProcNode.cs | 6 ----- src/Build/Microsoft.Build.csproj | 1 - 6 files changed, 2 insertions(+), 59 deletions(-) delete mode 100644 src/Build/BackEnd/Components/ResourceManager/ResourceManagerServiceFactory.cs diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index cf08dc532c5..b2157cf4e07 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2154,7 +2154,6 @@ private NodeConfiguration GetNodeConfiguration() , AppDomain.CurrentDomain.SetupInformation #endif , new LoggingNodeConfiguration(loggingService.IncludeEvaluationMetaprojects, loggingService.IncludeEvaluationProfile, loggingService.IncludeTaskInputs) - , "MSBuildCpuCount" ); } diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index cc83d182199..5a4b6bf5118 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -17,13 +17,6 @@ class ResourceManagerService : IBuildComponent { Semaphore? s = null; - private string _semaphoreName; - - public ResourceManagerService(string semaphoreName) - { - _semaphoreName = semaphoreName; - } - internal static IBuildComponent CreateComponent(BuildComponentType type) { ErrorUtilities.VerifyThrow(type == BuildComponentType.TaskResourceManager, "Cannot create components of type {0}", type); diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerServiceFactory.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerServiceFactory.cs deleted file mode 100644 index 3b3dc53a7c8..00000000000 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerServiceFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.Build.Shared; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -#nullable enable - -namespace Microsoft.Build.BackEnd.Components.ResourceManager -{ - class ResourceManagerServiceFactory - { - public IBuildComponent CreateInstance(BuildComponentType type) - { - // Create the instance of OutOfProcNodeSdkResolverService and pass parameters to the constructor. - return new ResourceManagerService(); - } - - } -} diff --git a/src/Build/BackEnd/Node/NodeConfiguration.cs b/src/Build/BackEnd/Node/NodeConfiguration.cs index ab0db7c232f..7052f8c4014 100644 --- a/src/Build/BackEnd/Node/NodeConfiguration.cs +++ b/src/Build/BackEnd/Node/NodeConfiguration.cs @@ -40,8 +40,6 @@ internal class NodeConfiguration : INodePacket /// private LoggingNodeConfiguration _loggingNodeConfiguration; - private string _resourceManagerSemaphoreName; - /// /// Constructor /// @@ -58,8 +56,7 @@ public NodeConfiguration #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif - LoggingNodeConfiguration loggingNodeConfiguration, - string resourceManagerSemaphoreName + LoggingNodeConfiguration loggingNodeConfiguration ) { _nodeId = nodeId; @@ -69,7 +66,6 @@ string resourceManagerSemaphoreName _appDomainSetup = appDomainSetup; #endif _loggingNodeConfiguration = loggingNodeConfiguration; - _resourceManagerSemaphoreName = resourceManagerSemaphoreName; } /// @@ -135,18 +131,7 @@ public LoggingNodeConfiguration LoggingNodeConfiguration { return _loggingNodeConfiguration; } } - /// - /// Name of the semaphore that communicates resource use between nodes. - /// - public string ResourceManagerSemaphoreName - { - [DebuggerStepThrough] - get - { return _resourceManagerSemaphoreName; } - - } - - #region INodePacket Members +#region INodePacket Members /// /// Retrieves the packet type. @@ -175,7 +160,6 @@ public void Translate(ITranslator translator) translator.TranslateDotNet(ref _appDomainSetup); #endif translator.Translate(ref _loggingNodeConfiguration); - translator.Translate(ref _resourceManagerSemaphoreName); } /// @@ -199,7 +183,6 @@ internal NodeConfiguration Clone() , _appDomainSetup #endif , _loggingNodeConfiguration - , _resourceManagerSemaphoreName ); } } diff --git a/src/Build/BackEnd/Node/OutOfProcNode.cs b/src/Build/BackEnd/Node/OutOfProcNode.cs index 073e1e3c842..be436db5431 100644 --- a/src/Build/BackEnd/Node/OutOfProcNode.cs +++ b/src/Build/BackEnd/Node/OutOfProcNode.cs @@ -18,7 +18,6 @@ using Microsoft.Build.BackEnd.Components.Caching; using Microsoft.Build.BackEnd.SdkResolution; using SdkResult = Microsoft.Build.BackEnd.SdkResolution.SdkResult; -using Microsoft.Build.BackEnd.Components.ResourceManager; namespace Microsoft.Build.Execution { @@ -640,11 +639,6 @@ private void HandleNodeConfiguration(NodeConfiguration configuration) // Grab the system parameters. _buildParameters = configuration.BuildParameters; - //configuration.ResourceManagerSemaphoreName - - ((IBuildComponentHost)this).RegisterFactory(BuildComponentType.TaskResourceManager, new ResourceManagerServiceFactory(configuration.ResourceManagerSemaphoreName).CreateInstance); - - _buildParameters.ProjectRootElementCache = s_projectRootElementCacheBase; // Snapshot the current environment diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index eccd3580691..5329a7386e3 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -157,7 +157,6 @@ - From 1ada7c2a134202712739e80620bb4c6b0be2f2b1 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Mon, 20 Apr 2020 10:52:57 -0500 Subject: [PATCH 016/105] Horrible pile of WIP hacks to debug hang --- .../Components/RequestBuilder/TaskBuilder.cs | 14 ++++++- .../Components/RequestBuilder/TaskHost.cs | 37 ++++++++++++++++--- .../ResourceManager/ResourceManagerService.cs | 32 +++++++++++++++- 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index bcb11463ae9..597d418b380 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -321,6 +321,8 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo WorkUnitResult aggregateResult = new WorkUnitResult(); + taskHost?.Log($"In ExecuteTask, project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}"); + // Loop through each of the batch buckets and execute them one at a time for (int i = 0; i < buckets.Count; i++) { @@ -346,7 +348,9 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo MSBuildEventSource.Log.ExecuteTaskStop(_taskNode?.Name, taskLoggingContext.BuildEventContext.TaskId); } } - + + taskHost?.Log($"ExecuteTask done, project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}"); + taskResult = aggregateResult; } finally @@ -759,6 +763,8 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta TaskExecutionHost host = taskExecutionHost as TaskExecutionHost; Type taskType = host.TaskInstance.GetType(); + taskHost?.Log($"ExecuteInstantiatedTask project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}, task is {taskType.Name}"); + try { if (taskType == typeof(MSBuild)) @@ -813,7 +819,13 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta using (FullTracking.Track(taskLoggingContext.TargetLoggingContext.Target.Name, _taskNode.Name, _buildRequestEntry.ProjectRootDirectory, _buildRequestEntry.RequestConfiguration.Project.PropertiesToBuildWith)) #endif { + taskHost?.Log($"In ExecuteInstantiatedTask, project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}"); + taskHost?.RequireCores(1); + taskResult = taskExecutionHost.Execute(); + + taskHost?.ReleaseCores(1); + taskHost?.Log($"ExecuteInstantiatedTask done, project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}"); } } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 0f93e233f57..2a100338546 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -120,6 +120,7 @@ public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, Elemen ErrorUtilities.VerifyThrowInternalNull(taskLocation, "taskLocation"); _host = host; + rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; _requestEntry = requestEntry; _taskLocation = taskLocation; _targetBuilderCallback = targetBuilderCallback; @@ -127,8 +128,10 @@ public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, Elemen _activeProxy = true; _callbackMonitor = new Object(); + Log($"TaskHost ctor for {requestEntry.RequestConfiguration.ConfigurationId}: {requestEntry.RequestConfiguration.ProjectFullPath}"); + // Ensure that we have at least one core to run this task - RequireCores(1); + //RequireCores(1); } /// @@ -347,6 +350,8 @@ public void Yield() { lock (_callbackMonitor) { + Log("Yielding"); + IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId == -1, "Cannot call Yield() while yielding."); _yieldThreadId = Thread.CurrentThread.ManagedThreadId; @@ -364,6 +369,7 @@ public void Reacquire() { lock (_callbackMonitor) { + Log("reacquring"); IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId != -1, "Cannot call Reacquire() before Yield()."); ErrorUtilities.VerifyThrow(_yieldThreadId == Thread.CurrentThread.ManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Thread.CurrentThread.ManagedThreadId, _yieldThreadId); @@ -371,6 +377,7 @@ public void Reacquire() MSBuildEventSource.Log.ExecuteTaskReacquireStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); builderCallback.Reacquire(); MSBuildEventSource.Log.ExecuteTaskReacquireStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); + Log("reacquired"); _yieldThreadId = -1; } } @@ -672,6 +679,10 @@ public void LogTelemetry(string eventName, IDictionary propertie int runningTotal = 0; + ResourceManagerService rms; + + public void Log(string s) => rms.Log($"{s}, runningTotal={runningTotal}"); + public int RequestCores(int requestedCores) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; @@ -682,37 +693,53 @@ public int RequestCores(int requestedCores) runningTotal += coresAcquired; + Log($"Requested {requestedCores}, got {coresAcquired}"); + return coresAcquired; } - private void RequireCores(int requestedCores) + public void RequireCores(int requestedCores) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; rms.RequireCores(requestedCores); - runningTotal += 1; // default reservation + runningTotal += requestedCores; // default reservation + + Log($"Required {requestedCores}"); + } public void ReleaseCores(int coresToRelease) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; + Log($"Attempting to release {coresToRelease}"); coresToRelease = Math.Min(runningTotal, coresToRelease); if (coresToRelease >= 1) { - runningTotal -= coresToRelease; - rms.ReleaseCores(coresToRelease); + runningTotal -= coresToRelease; } + + Log($"Released {coresToRelease}"); } internal void ReleaseAllCores() { + Log("Releasing all"); ReleaseCores(runningTotal); + var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; + + if (rms.TotalNumberHeld != 0) + { + //Debug.Fail("still holding"); + } + runningTotal = 0; + Log("all released"); } /// diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 5a4b6bf5118..37c51c7ea96 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -4,6 +4,7 @@ using Microsoft.Build.Shared; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; @@ -17,6 +18,12 @@ class ResourceManagerService : IBuildComponent { Semaphore? s = null; + public int TotalNumberHeld = -1; + + private static StringBuilder log = new StringBuilder(); + + public void Log(string s) => log.AppendFormat("{0}: {1}, current={2} thread={4} {3}", DateTime.Now.Ticks, s, TotalNumberHeld, Environment.NewLine, Thread.CurrentThread.ManagedThreadId); + internal static IBuildComponent CreateComponent(BuildComponentType type) { ErrorUtilities.VerifyThrow(type == BuildComponentType.TaskResourceManager, "Cannot create components of type {0}", type); @@ -30,6 +37,10 @@ public void InitializeComponent(IBuildComponentHost host) int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability + TotalNumberHeld = 0; + + Log($"Initialized with {resourceCount}"); + s = new Semaphore(resourceCount, resourceCount, SemaphoreName); // TODO: SemaphoreSecurity? } @@ -37,10 +48,16 @@ public void ShutdownComponent() { s?.Dispose(); s = null; + + Log($"Tearing down; held should have been {TotalNumberHeld}"); + + TotalNumberHeld = -2; } public int RequestCores(int requestedCores) { + Log($"Requesting {requestedCores}"); + if (s is null) { // TODO: ErrorUtilities should be annotated so this can just be `ErrorUtilities.VerifyThrow` @@ -59,6 +76,8 @@ public int RequestCores(int requestedCores) } } + Log($"got {i}, holding {TotalNumberHeld}"); + return i; } @@ -74,6 +93,10 @@ public void ReleaseCores(int coresToRelease) ErrorUtilities.VerifyThrow(coresToRelease > 0, "Tried to release {0} cores", coresToRelease); s.Release(coresToRelease); + + TotalNumberHeld -= coresToRelease; + + Log($"released {coresToRelease}, now holding {TotalNumberHeld}"); } internal void RequireCores(int requestedCores) @@ -85,11 +108,18 @@ internal void RequireCores(int requestedCores) throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); } + if (TotalNumberHeld >= 1) + { + //Debugger.Launch(); + } + if (!s.WaitOne()) { ErrorUtilities.ThrowInternalError("Couldn't get a core to run a task even with infinite timeout"); - } + + TotalNumberHeld++; + Log($"required 1, now holding {TotalNumberHeld}"); } } } From e9436558ba0ebaad880d85f2a6faf282dbc9ee32 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Mon, 20 Apr 2020 10:53:02 -0500 Subject: [PATCH 017/105] Revert "Horrible pile of WIP hacks to debug hang" This reverts commit 3c7a3f87ee22011cfd63090960fee0c7a55c2d22. --- .../Components/RequestBuilder/TaskBuilder.cs | 14 +------ .../Components/RequestBuilder/TaskHost.cs | 37 +++---------------- .../ResourceManager/ResourceManagerService.cs | 32 +--------------- 3 files changed, 7 insertions(+), 76 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index 597d418b380..bcb11463ae9 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -321,8 +321,6 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo WorkUnitResult aggregateResult = new WorkUnitResult(); - taskHost?.Log($"In ExecuteTask, project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}"); - // Loop through each of the batch buckets and execute them one at a time for (int i = 0; i < buckets.Count; i++) { @@ -348,9 +346,7 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo MSBuildEventSource.Log.ExecuteTaskStop(_taskNode?.Name, taskLoggingContext.BuildEventContext.TaskId); } } - - taskHost?.Log($"ExecuteTask done, project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}"); - + taskResult = aggregateResult; } finally @@ -763,8 +759,6 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta TaskExecutionHost host = taskExecutionHost as TaskExecutionHost; Type taskType = host.TaskInstance.GetType(); - taskHost?.Log($"ExecuteInstantiatedTask project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}, task is {taskType.Name}"); - try { if (taskType == typeof(MSBuild)) @@ -819,13 +813,7 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta using (FullTracking.Track(taskLoggingContext.TargetLoggingContext.Target.Name, _taskNode.Name, _buildRequestEntry.ProjectRootDirectory, _buildRequestEntry.RequestConfiguration.Project.PropertiesToBuildWith)) #endif { - taskHost?.Log($"In ExecuteInstantiatedTask, project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}"); - taskHost?.RequireCores(1); - taskResult = taskExecutionHost.Execute(); - - taskHost?.ReleaseCores(1); - taskHost?.Log($"ExecuteInstantiatedTask done, project instance {_targetLoggingContext?.BuildEventContext?.ProjectInstanceId} target {_targetLoggingContext?.BuildEventContext?.TargetId}"); } } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 2a100338546..0f93e233f57 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -120,7 +120,6 @@ public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, Elemen ErrorUtilities.VerifyThrowInternalNull(taskLocation, "taskLocation"); _host = host; - rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; _requestEntry = requestEntry; _taskLocation = taskLocation; _targetBuilderCallback = targetBuilderCallback; @@ -128,10 +127,8 @@ public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, Elemen _activeProxy = true; _callbackMonitor = new Object(); - Log($"TaskHost ctor for {requestEntry.RequestConfiguration.ConfigurationId}: {requestEntry.RequestConfiguration.ProjectFullPath}"); - // Ensure that we have at least one core to run this task - //RequireCores(1); + RequireCores(1); } /// @@ -350,8 +347,6 @@ public void Yield() { lock (_callbackMonitor) { - Log("Yielding"); - IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId == -1, "Cannot call Yield() while yielding."); _yieldThreadId = Thread.CurrentThread.ManagedThreadId; @@ -369,7 +364,6 @@ public void Reacquire() { lock (_callbackMonitor) { - Log("reacquring"); IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId != -1, "Cannot call Reacquire() before Yield()."); ErrorUtilities.VerifyThrow(_yieldThreadId == Thread.CurrentThread.ManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Thread.CurrentThread.ManagedThreadId, _yieldThreadId); @@ -377,7 +371,6 @@ public void Reacquire() MSBuildEventSource.Log.ExecuteTaskReacquireStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); builderCallback.Reacquire(); MSBuildEventSource.Log.ExecuteTaskReacquireStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); - Log("reacquired"); _yieldThreadId = -1; } } @@ -679,10 +672,6 @@ public void LogTelemetry(string eventName, IDictionary propertie int runningTotal = 0; - ResourceManagerService rms; - - public void Log(string s) => rms.Log($"{s}, runningTotal={runningTotal}"); - public int RequestCores(int requestedCores) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; @@ -693,53 +682,37 @@ public int RequestCores(int requestedCores) runningTotal += coresAcquired; - Log($"Requested {requestedCores}, got {coresAcquired}"); - return coresAcquired; } - public void RequireCores(int requestedCores) + private void RequireCores(int requestedCores) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; rms.RequireCores(requestedCores); - runningTotal += requestedCores; // default reservation - - Log($"Required {requestedCores}"); - + runningTotal += 1; // default reservation } public void ReleaseCores(int coresToRelease) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; - Log($"Attempting to release {coresToRelease}"); coresToRelease = Math.Min(runningTotal, coresToRelease); if (coresToRelease >= 1) { - rms.ReleaseCores(coresToRelease); runningTotal -= coresToRelease; - } - Log($"Released {coresToRelease}"); + rms.ReleaseCores(coresToRelease); + } } internal void ReleaseAllCores() { - Log("Releasing all"); ReleaseCores(runningTotal); - var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; - - if (rms.TotalNumberHeld != 0) - { - //Debug.Fail("still holding"); - } - runningTotal = 0; - Log("all released"); } /// diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 37c51c7ea96..5a4b6bf5118 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -4,7 +4,6 @@ using Microsoft.Build.Shared; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; @@ -18,12 +17,6 @@ class ResourceManagerService : IBuildComponent { Semaphore? s = null; - public int TotalNumberHeld = -1; - - private static StringBuilder log = new StringBuilder(); - - public void Log(string s) => log.AppendFormat("{0}: {1}, current={2} thread={4} {3}", DateTime.Now.Ticks, s, TotalNumberHeld, Environment.NewLine, Thread.CurrentThread.ManagedThreadId); - internal static IBuildComponent CreateComponent(BuildComponentType type) { ErrorUtilities.VerifyThrow(type == BuildComponentType.TaskResourceManager, "Cannot create components of type {0}", type); @@ -37,10 +30,6 @@ public void InitializeComponent(IBuildComponentHost host) int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability - TotalNumberHeld = 0; - - Log($"Initialized with {resourceCount}"); - s = new Semaphore(resourceCount, resourceCount, SemaphoreName); // TODO: SemaphoreSecurity? } @@ -48,16 +37,10 @@ public void ShutdownComponent() { s?.Dispose(); s = null; - - Log($"Tearing down; held should have been {TotalNumberHeld}"); - - TotalNumberHeld = -2; } public int RequestCores(int requestedCores) { - Log($"Requesting {requestedCores}"); - if (s is null) { // TODO: ErrorUtilities should be annotated so this can just be `ErrorUtilities.VerifyThrow` @@ -76,8 +59,6 @@ public int RequestCores(int requestedCores) } } - Log($"got {i}, holding {TotalNumberHeld}"); - return i; } @@ -93,10 +74,6 @@ public void ReleaseCores(int coresToRelease) ErrorUtilities.VerifyThrow(coresToRelease > 0, "Tried to release {0} cores", coresToRelease); s.Release(coresToRelease); - - TotalNumberHeld -= coresToRelease; - - Log($"released {coresToRelease}, now holding {TotalNumberHeld}"); } internal void RequireCores(int requestedCores) @@ -108,18 +85,11 @@ internal void RequireCores(int requestedCores) throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); } - if (TotalNumberHeld >= 1) - { - //Debugger.Launch(); - } - if (!s.WaitOne()) { ErrorUtilities.ThrowInternalError("Couldn't get a core to run a task even with infinite timeout"); - } - TotalNumberHeld++; - Log($"required 1, now holding {TotalNumberHeld}"); + } } } } From ca6697d34081a4598e3137a5fbeacaa0c2304240 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Mon, 20 Apr 2020 10:59:03 -0500 Subject: [PATCH 018/105] Move requiring core to ExecuteInstantiatedTask This should alleviate hangs that were happening as a result of leaks that happened when MSBuild/CallTarget got a resource, then we started building other projects. --- .../Components/RequestBuilder/TaskBuilder.cs | 4 +++- .../Components/RequestBuilder/TaskHost.cs | 10 +++------ .../ResourceManager/ResourceManagerService.cs | 22 ++++++++++++++++++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index bcb11463ae9..0ce84cfc277 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -346,7 +346,7 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo MSBuildEventSource.Log.ExecuteTaskStop(_taskNode?.Name, taskLoggingContext.BuildEventContext.TaskId); } } - + taskResult = aggregateResult; } finally @@ -813,7 +813,9 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta using (FullTracking.Track(taskLoggingContext.TargetLoggingContext.Target.Name, _taskNode.Name, _buildRequestEntry.ProjectRootDirectory, _buildRequestEntry.RequestConfiguration.Project.PropertiesToBuildWith)) #endif { + taskHost?.RequireCores(1); taskResult = taskExecutionHost.Execute(); + taskHost?.ReleaseCores(1); } } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 0f93e233f57..b7d32396d35 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -126,9 +126,6 @@ public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, Elemen _continueOnError = false; _activeProxy = true; _callbackMonitor = new Object(); - - // Ensure that we have at least one core to run this task - RequireCores(1); } /// @@ -685,13 +682,13 @@ public int RequestCores(int requestedCores) return coresAcquired; } - private void RequireCores(int requestedCores) + public void RequireCores(int requestedCores) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; rms.RequireCores(requestedCores); - runningTotal += 1; // default reservation + runningTotal += requestedCores; // default reservation } public void ReleaseCores(int coresToRelease) @@ -702,9 +699,8 @@ public void ReleaseCores(int coresToRelease) if (coresToRelease >= 1) { - runningTotal -= coresToRelease; - rms.ReleaseCores(coresToRelease); + runningTotal -= coresToRelease; } } diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 5a4b6bf5118..69ebd6a3063 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -4,6 +4,7 @@ using Microsoft.Build.Shared; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; @@ -17,6 +18,10 @@ class ResourceManagerService : IBuildComponent { Semaphore? s = null; +#if DEBUG + public int TotalNumberHeld = -1; +#endif + internal static IBuildComponent CreateComponent(BuildComponentType type) { ErrorUtilities.VerifyThrow(type == BuildComponentType.TaskResourceManager, "Cannot create components of type {0}", type); @@ -30,6 +35,10 @@ public void InitializeComponent(IBuildComponentHost host) int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability +#if DEBUG + TotalNumberHeld = 0; +#endif + s = new Semaphore(resourceCount, resourceCount, SemaphoreName); // TODO: SemaphoreSecurity? } @@ -37,6 +46,10 @@ public void ShutdownComponent() { s?.Dispose(); s = null; + +#if DEBUG + TotalNumberHeld = -2; +#endif } public int RequestCores(int requestedCores) @@ -74,6 +87,10 @@ public void ReleaseCores(int coresToRelease) ErrorUtilities.VerifyThrow(coresToRelease > 0, "Tried to release {0} cores", coresToRelease); s.Release(coresToRelease); + +#if DEBUG + TotalNumberHeld -= coresToRelease; +#endif } internal void RequireCores(int requestedCores) @@ -88,8 +105,11 @@ internal void RequireCores(int requestedCores) if (!s.WaitOne()) { ErrorUtilities.ThrowInternalError("Couldn't get a core to run a task even with infinite timeout"); - } + +#if DEBUG + TotalNumberHeld++; +#endif } } } From 09618ecb527d29512fced7d40e9bcc015015870b Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 1 May 2020 17:33:39 -0500 Subject: [PATCH 019/105] Release core when yielding (hopefully working around hang when many threads yielded + reacquiring) --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 850a818a9ba..884a150edd2 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -348,6 +348,8 @@ public void Yield() { lock (_callbackMonitor) { + ReleaseCores(1); + IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId == -1, "Cannot call Yield() while yielding."); _yieldThreadId = Thread.CurrentThread.ManagedThreadId; @@ -370,6 +372,9 @@ public void Reacquire() ErrorUtilities.VerifyThrow(_yieldThreadId == Thread.CurrentThread.ManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Thread.CurrentThread.ManagedThreadId, _yieldThreadId); MSBuildEventSource.Log.ExecuteTaskYieldStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); MSBuildEventSource.Log.ExecuteTaskReacquireStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); + + //TODO: should this RequireCores(1)? + builderCallback.Reacquire(); MSBuildEventSource.Log.ExecuteTaskReacquireStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); _yieldThreadId = -1; From 5a7ad2be2751191455ca470145a1ce77cf37b2a4 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 10 Jun 2020 09:45:19 -0500 Subject: [PATCH 020/105] WIP: new semaphore name per session (by default) --- ref/Microsoft.Build/net/Microsoft.Build.cs | 1 + ref/Microsoft.Build/netstandard/Microsoft.Build.cs | 1 + src/Build/BackEnd/BuildManager/BuildParameters.cs | 8 ++++++++ .../Components/ResourceManager/ResourceManagerService.cs | 6 ++++-- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ref/Microsoft.Build/net/Microsoft.Build.cs b/ref/Microsoft.Build/net/Microsoft.Build.cs index b8440d4530d..5e4da25caa7 100644 --- a/ref/Microsoft.Build/net/Microsoft.Build.cs +++ b/ref/Microsoft.Build/net/Microsoft.Build.cs @@ -994,6 +994,7 @@ public partial class BuildParameters public string OutputResultsCacheFile { get { throw null; } set { } } public Microsoft.Build.Evaluation.ProjectLoadSettings ProjectLoadSettings { get { throw null; } set { } } public bool ResetCaches { get { throw null; } set { } } + public string ResourceManagerSemaphoreName { get { throw null; } set { } } public bool SaveOperatingEnvironment { get { throw null; } set { } } public bool ShutdownInProcNodeOnBuildFinish { get { throw null; } set { } } public Microsoft.Build.Evaluation.ToolsetDefinitionLocations ToolsetDefinitionLocations { get { throw null; } set { } } diff --git a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs index 5a215a8c307..9744334fb4e 100644 --- a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs +++ b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs @@ -989,6 +989,7 @@ public partial class BuildParameters public string OutputResultsCacheFile { get { throw null; } set { } } public Microsoft.Build.Evaluation.ProjectLoadSettings ProjectLoadSettings { get { throw null; } set { } } public bool ResetCaches { get { throw null; } set { } } + public string ResourceManagerSemaphoreName { get { throw null; } set { } } public bool SaveOperatingEnvironment { get { throw null; } set { } } public bool ShutdownInProcNodeOnBuildFinish { get { throw null; } set { } } public Microsoft.Build.Evaluation.ToolsetDefinitionLocations ToolsetDefinitionLocations { get { throw null; } set { } } diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index 44a8f200613..090cb82acf9 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -214,6 +214,7 @@ public class BuildParameters : ITranslatable private string[] _inputResultsCacheFiles; private string _outputResultsCacheFile; + private string _resourceManagerSemaphoreName = $"MSBuild.{Guid.NewGuid().ToString()}"; /// /// Constructor for those who intend to set all properties themselves. @@ -765,6 +766,12 @@ public string OutputResultsCacheFile set => _outputResultsCacheFile = value; } + public string ResourceManagerSemaphoreName + { + get => _resourceManagerSemaphoreName; + set => _resourceManagerSemaphoreName = value; + } + /// /// Determines whether MSBuild will save the results of builds after EndBuild to speed up future builds. /// @@ -833,6 +840,7 @@ void ITranslatable.Translate(ITranslator translator) translator.TranslateEnum(ref _projectLoadSettings, (int) _projectLoadSettings); translator.Translate(ref _interactive); translator.Translate(ref _isolateProjects); + translator.Translate(ref _resourceManagerSemaphoreName); // ProjectRootElementCache is not transmitted. // ResetCaches is not transmitted. diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 69ebd6a3063..9702c97ac9c 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -20,6 +20,7 @@ class ResourceManagerService : IBuildComponent #if DEBUG public int TotalNumberHeld = -1; + public string? SemaphoreName; #endif internal static IBuildComponent CreateComponent(BuildComponentType type) @@ -31,15 +32,16 @@ internal static IBuildComponent CreateComponent(BuildComponentType type) public void InitializeComponent(IBuildComponentHost host) { - const string SemaphoreName = "cpuCount"; // TODO + string semaphoreName = host.BuildParameters.ResourceManagerSemaphoreName; int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability #if DEBUG TotalNumberHeld = 0; + SemaphoreName = semaphoreName; #endif - s = new Semaphore(resourceCount, resourceCount, SemaphoreName); // TODO: SemaphoreSecurity? + s = new Semaphore(resourceCount, resourceCount, semaphoreName); // TODO: SemaphoreSecurity? } public void ShutdownComponent() From 007d9f3b9ce7ea1cb39e0b92e4f7b23a1e64205b Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 16 Jun 2020 10:51:53 -0500 Subject: [PATCH 021/105] Release a core when calling BuildProjectFiles Otherwise, we can deadlock: outer task gets a resource, (logically) yields to build other projects; they block on getting a resource. --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 1e591e8ce29..1635d745476 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -912,6 +912,8 @@ private async Task BuildProjectFilesInParallelAsync(string[] List> targetOutputsPerProject = null; + ReleaseCores(1); // TODO: all? + #if FEATURE_FILE_TRACKER using (FullTracking.Suspend()) #endif From 6b2be21676459677772192a0ba4f8c10d33e1e44 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 16 Jun 2020 10:52:55 -0500 Subject: [PATCH 022/105] Switch expression for MockHost.GetComponent --- src/Build.UnitTests/BackEnd/MockHost.cs | 35 +++++++------------------ 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/MockHost.cs b/src/Build.UnitTests/BackEnd/MockHost.cs index ed2b547c0af..388a44f149a 100644 --- a/src/Build.UnitTests/BackEnd/MockHost.cs +++ b/src/Build.UnitTests/BackEnd/MockHost.cs @@ -163,32 +163,17 @@ internal IRequestBuilder RequestBuilder /// public IBuildComponent GetComponent(BuildComponentType type) { - switch (type) + return type switch { - case BuildComponentType.ConfigCache: - return (IBuildComponent)_configCache; - - case BuildComponentType.LoggingService: - return (IBuildComponent)_loggingService; - - case BuildComponentType.RequestEngine: - return (IBuildComponent)_requestEngine; - - case BuildComponentType.TargetBuilder: - return (IBuildComponent)_targetBuilder; - - case BuildComponentType.ResultsCache: - return (IBuildComponent)_resultsCache; - - case BuildComponentType.RequestBuilder: - return (IBuildComponent)_requestBuilder; - - case BuildComponentType.SdkResolverService: - return (IBuildComponent)_sdkResolverService; - - default: - throw new ArgumentException("Unexpected type " + type); - } + BuildComponentType.ConfigCache => (IBuildComponent)_configCache, + BuildComponentType.LoggingService => (IBuildComponent)_loggingService, + BuildComponentType.RequestEngine => (IBuildComponent)_requestEngine, + BuildComponentType.TargetBuilder => (IBuildComponent)_targetBuilder, + BuildComponentType.ResultsCache => (IBuildComponent)_resultsCache, + BuildComponentType.RequestBuilder => (IBuildComponent)_requestBuilder, + BuildComponentType.SdkResolverService => (IBuildComponent)_sdkResolverService, + _ => throw new ArgumentException("Unexpected type " + type), + }; } /// From 5d4987646032d3729a8ebd7e319d9c0679aabc13 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 16 Jun 2020 11:00:20 -0500 Subject: [PATCH 023/105] Resource manager in MockHost --- src/Build.UnitTests/BackEnd/MockHost.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Build.UnitTests/BackEnd/MockHost.cs b/src/Build.UnitTests/BackEnd/MockHost.cs index 388a44f149a..272a388b8be 100644 --- a/src/Build.UnitTests/BackEnd/MockHost.cs +++ b/src/Build.UnitTests/BackEnd/MockHost.cs @@ -11,6 +11,7 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using LegacyThreadingData = Microsoft.Build.Execution.LegacyThreadingData; +using Microsoft.Build.BackEnd.Components.ResourceManager; namespace Microsoft.Build.UnitTests.BackEnd { @@ -61,6 +62,8 @@ internal class MockHost : MockLoggingService, IBuildComponentHost, IBuildCompone private ISdkResolverService _sdkResolverService; + private readonly ResourceManagerService _taskResourceManager; + #region SystemParameterFields #endregion; @@ -104,6 +107,9 @@ public MockHost(BuildParameters buildParameters) _sdkResolverService = new MockSdkResolverService(); ((IBuildComponent)_sdkResolverService).InitializeComponent(this); + + _taskResourceManager = new ResourceManagerService(); + ((IBuildComponent)_taskResourceManager).InitializeComponent(this); } /// @@ -172,6 +178,7 @@ public IBuildComponent GetComponent(BuildComponentType type) BuildComponentType.ResultsCache => (IBuildComponent)_resultsCache, BuildComponentType.RequestBuilder => (IBuildComponent)_requestBuilder, BuildComponentType.SdkResolverService => (IBuildComponent)_sdkResolverService, + BuildComponentType.TaskResourceManager => (IBuildComponent)_taskResourceManager, _ => throw new ArgumentException("Unexpected type " + type), }; } From 0486730db23ffbaadb38582971625674ba6ef2bf Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 16 Jun 2020 12:14:59 -0500 Subject: [PATCH 024/105] Just don't do resource management on non-Windows --- .../ResourceManager/ResourceManagerService.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 9702c97ac9c..e9d38447506 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -41,7 +41,15 @@ public void InitializeComponent(IBuildComponentHost host) SemaphoreName = semaphoreName; #endif - s = new Semaphore(resourceCount, resourceCount, semaphoreName); // TODO: SemaphoreSecurity? + if (NativeMethodsShared.IsWindows) + { + s = new Semaphore(resourceCount, resourceCount, semaphoreName); // TODO: SemaphoreSecurity? + } + else + { + // UNDONE: just don't support gathering additional cores on non-Windows + s = new Semaphore(1, 1); + } } public void ShutdownComponent() @@ -58,6 +66,11 @@ public int RequestCores(int requestedCores) { if (s is null) { + if (!NativeMethodsShared.IsWindows) + { + return 0; + } + // TODO: ErrorUtilities should be annotated so this can just be `ErrorUtilities.VerifyThrow` // https://github.com/microsoft/msbuild/issues/5163 throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); From 2f85924da1613b317fdb880de974ba677486afbf Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 16 Jun 2020 14:19:30 -0500 Subject: [PATCH 025/105] Delete bogus tests --- src/Tasks.UnitTests/Semaphore_Tests.cs | 70 -------------------------- 1 file changed, 70 deletions(-) delete mode 100644 src/Tasks.UnitTests/Semaphore_Tests.cs diff --git a/src/Tasks.UnitTests/Semaphore_Tests.cs b/src/Tasks.UnitTests/Semaphore_Tests.cs deleted file mode 100644 index 57e64f13c66..00000000000 --- a/src/Tasks.UnitTests/Semaphore_Tests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit; -using Shouldly; -using Microsoft.Build.UnitTests; -using System.Threading; - -namespace Microsoft.Build.Tasks.UnitTests -{ - public class Semaphore_Tests - { - [Fact] - public void TestRequestingInvalidNumCores() - { - // assume multiproc build of 40 - new Semaphore(40, 40, "cpuCount"); - MockEngine mockEngine = new MockEngine(); - - SemaphoreCPUTask test = new SemaphoreCPUTask(); - test.BuildEngine = mockEngine; - - // 40 - 80 = 0 cores left (claimed 40) - test.BuildEngine7.RequestCores(12312).ShouldBe(40); - test.BuildEngine7.RequestCores(10).ShouldBe(0); - - // 0 + 39 = 39 cores left - test.BuildEngine7.ReleaseCores(39); - - // 39 - 100 = 0 cores left (claimed 39) - test.BuildEngine7.RequestCores(100).ShouldBe(39); - - // 0 + 0 = 0 cores left - test.BuildEngine7.ReleaseCores(0); - test.BuildEngine7.RequestCores(2).ShouldBe(0); - - //0 + 1 = 1 cores left - test.BuildEngine7.ReleaseCores(1); - - // 1 - 2 = 0 cores left (only claimed 1) - test.BuildEngine7.RequestCores(2).ShouldBe(1); - } - - [Fact(Skip = "TODO: test harness to tweak number of assignable cores")] - public void TestReleasingInvalidNumCores() - { - // assume multiproc build of 40 - new Semaphore(40, 40, "cpuCount"); - MockEngine mockEngine = new MockEngine(); - - SemaphoreCPUTask test = new SemaphoreCPUTask(); - test.BuildEngine = mockEngine; - - // should still be 40 cores - test.BuildEngine7.ReleaseCores(-100); - test.BuildEngine7.RequestCores(41).ShouldBe(40); - - // should be 40 cores to take - test.BuildEngine7.ReleaseCores(50); - test.BuildEngine7.RequestCores(39).ShouldBe(39); - - test.BuildEngine7.RequestCores(2).ShouldBe(1); - } - } -} From 5e4b0357a05b5dc3d7521f117ba7faad63fad38c Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 17 Jun 2020 12:47:11 -0500 Subject: [PATCH 026/105] Doc for RequestCores --- documentation/specs/resource-management.md | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 documentation/specs/resource-management.md diff --git a/documentation/specs/resource-management.md b/documentation/specs/resource-management.md new file mode 100644 index 00000000000..26ce5aac954 --- /dev/null +++ b/documentation/specs/resource-management.md @@ -0,0 +1,45 @@ +# Managing tools with their own parallelism in MSBuild + +MSBuild supports building projects in parallel using multiple processes. Most users opt into `NUM_PROCS` parallelism at the MSBuild layer. + +In addition, tools sometimes support parallel execution. The Visual C++ compiler `cl.exe` supports `/MP[n]`, which parallelizes compilation at the translation-unit (file) level. If a number isn't specified, it defaults to `NUM_PROCS`. + +When used in combination, `NUM_PROCS * NUM_PROCS` compiler processes can be launched, all of which would like to do file I/O and intense computation. This generally overwhelms the operating system's scheduler and causes thrashing and terrible build times. + +As a result, the standard guidance is to use only one multiproc option: MSBuild's _or_ `cl.exe`'s. But that leaves the machine underloaded when things could be happening in parallel. + +## Design + +`IBuildEngine` will be extended to allow a task to indicate to MSBuild that it would like to consume more than one CPU core (`RequestCores`). These will be advisory only—a task can still do as much work as it desires with as many threads and processes as it desires. + +A cooperating task would limit its own parallelism to the number of CPU cores MSBuild can reserve for the requesting task. + +All resources acquired by a task will be automatically returned when the task's `Execute()` method returns, but a task can optionally return a subset by calling `ReleaseCores`. + +MSBuild will respect core reservations given to tasks for task execution only. If a project/task is eligible for execution but has not yet started when other tasks consume all resources, MSBuild will wait until a resource is freed before starting execution of the new task. This is required to allow tasks in multiprocess builds to acquire resources that are allocated to worker nodes that are currently blocked on references to other projects. + +When a task `Yield()`s, it releases 1 core reservation. When a task requests that the engine build another project it does likewise. Otherwise there could be a deadlock: a task is started (requiring the default 1 core), then yields/builds another project, then that tries to start a task (requiring the default 1 core), but that resource is held by the now-yielded task. + +## Example + +In a 16-process build of a solution with 30 projects, 16 worker nodes are launched and begin executing work. Most block on dependencies to projects `A`, `B`, `C`, `D`, and `E`, releasing their resource reservation. + +Task `Work` is called in project `A` with 25 inputs. It would like to run as many as possible in parallel. It calls + +```C# +int allowedParallelism = BuildEngine7.RequestCores(Inputs.Count); // Inputs.Count == 25 +``` + +MSBuild is busy building tasks in projects `B`, `C`, `D`, and `E`, but many nodes are blocked on references to `A`, `B`, `C`, `D`, and `E`, so it is actively building 4 other tasks. As a result, the engine can dedicate 12 cores to `Work`. The call to `RequestCores` returns `11` (total possible parallelism `16` - other running tasks `4` - default reservation for the current task `1`). + +`Work` can then do 12 cores worth of computation. + +While `Work` has the 12-core reservation, another project completes a task execution and calls a new task `Work2`. This task calls `RequestCores`, but MSBuild has no spare allocation and returns `0`, causing `Work2` to begin its computation serially. + +When `Work` returns, MSBuild automatically returns all resources reserved by the task to the pool. Before moving on with its processing, `Work2` calls `RequestCores` again, and this time receives a larger reservation. + +## Implementation + +The initial implementation of the system will use a Win32 [named semaphore](https://docs.microsoft.com/windows/win32/sync/semaphore-objects) to track resource use. This was the implementation of `MultiToolTask` in the VC++ toolchain and is a performant implementation of a counter that can be shared across processes. + +On platforms where named semaphores are not supported (.NET Core MSBuild running on macOS, Linux, or other UNIXes), `RequestCores` will always return `0`. We will consider implementing full support using a cross-process semaphore (or an addition to the existing MSBuild communication protocol, if it isn't prohibitively costly to do the packet exchange and processing) on these platforms in the future. From 479cdfcd1a78b8e671f4e47e0dcaba03daca047d Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 15 Jul 2020 15:55:28 -0500 Subject: [PATCH 027/105] Add BlockingWaitForCore --- .../net/Microsoft.Build.Framework.cs | 1 + .../netstandard/Microsoft.Build.Framework.cs | 1 + .../Components/RequestBuilder/TaskHost.cs | 7 ++ src/Framework/IBuildEngine7.cs | 5 ++ src/MSBuild/OutOfProcTaskHostNode.cs | 5 ++ src/Shared/UnitTests/MockEngine.cs | 6 ++ src/Tasks/SemaphoreCPUTask.cs | 64 +++++++++++++------ 7 files changed, 70 insertions(+), 19 deletions(-) diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs index 56a5374abe6..914f1bd7dc9 100644 --- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs @@ -206,6 +206,7 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 { bool AllowFailureWithoutError { get; set; } + void BlockingWaitForCore(); void ReleaseCores(int coresToRelease); int RequestCores(int requestedCores); } diff --git a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs index a3c7a133d10..f2ade095604 100644 --- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs @@ -206,6 +206,7 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 { bool AllowFailureWithoutError { get; set; } + void BlockingWaitForCore(); void ReleaseCores(int coresToRelease); int RequestCores(int requestedCores); } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 1635d745476..44fa9699dcc 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -715,6 +715,13 @@ public void ReleaseCores(int coresToRelease) } } + public void BlockingWaitForCore() + { + var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; + + rms.RequireCores(1); + } + internal void ReleaseAllCores() { ReleaseCores(runningTotal); diff --git a/src/Framework/IBuildEngine7.cs b/src/Framework/IBuildEngine7.cs index 025b60a6fbc..cb7bc8688d2 100644 --- a/src/Framework/IBuildEngine7.cs +++ b/src/Framework/IBuildEngine7.cs @@ -25,5 +25,10 @@ public interface IBuildEngine7 : IBuildEngine6 /// Number of cores no longer in use. void ReleaseCores(int coresToRelease); + /// + /// Block until a "core" from the managed pool for this build is available. + /// + void BlockingWaitForCore(); + } } diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index da75a8716a6..adac8bf4f72 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -1182,5 +1182,10 @@ public void ReleaseCores(int coresToRelease) { throw new NotImplementedException(); } + + public void BlockingWaitForCore() + { + throw new NotImplementedException(); + } } } diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index bd745cab9e2..4e44324afb0 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -522,7 +522,13 @@ public void ReleaseCores(int coresToRelease) { cpuCount.Release(coresToRelease); } + } + + public void BlockingWaitForCore() + { + cpuCount ??= Semaphore.OpenExisting("cpuCount"); + cpuCount.WaitOne(); } } } diff --git a/src/Tasks/SemaphoreCPUTask.cs b/src/Tasks/SemaphoreCPUTask.cs index 89f00bd0a0d..177e2c07fce 100644 --- a/src/Tasks/SemaphoreCPUTask.cs +++ b/src/Tasks/SemaphoreCPUTask.cs @@ -9,33 +9,59 @@ namespace Microsoft.Build.Tasks { class SemaphoreCPUTask : Task { + private const int Repetitions = 20; + public override bool Execute() { - int initial = BuildEngine7.RequestCores(3123890); - Log.LogMessageFromText($"Got {initial} cores from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + BuildEngine7.Yield(); - if (initial > 0) - { - while (initial > 0) - { - Thread.Sleep(TimeSpan.FromSeconds(1)); - BuildEngine7.ReleaseCores(1); - initial--; - Log.LogMessageFromText($"Released 1 core from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); - } - - return !Log.HasLoggedErrors; - } + //int initial = BuildEngine7.RequestCores(3123890); + //Log.LogMessageFromText($"Got {initial} cores from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + + //if (initial > 0) + //{ + // while (initial > 0) + // { + // Thread.Sleep(TimeSpan.FromSeconds(1)); + // BuildEngine7.ReleaseCores(1); + // initial--; + // Log.LogMessageFromText($"Released 1 core from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + // } + + // return !Log.HasLoggedErrors; + //} + + //for (int i = 0; i < 20; i++) + //{ + // int v = BuildEngine7.RequestCores(9999); + // Log.LogMessageFromText($"Got {v} cores from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + // BuildEngine7.ReleaseCores(v + 20); + // Thread.Sleep(TimeSpan.FromSeconds(0.9)); + //} + + System.Threading.Tasks.Task[] tasks = new System.Threading.Tasks.Task[Repetitions]; - for (int i = 0; i < 20; i++) + for (int i = 0; i < Repetitions; i++) { - int v = BuildEngine7.RequestCores(9999); - Log.LogMessageFromText($"Got {v} cores from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); - BuildEngine7.ReleaseCores(v + 20); - Thread.Sleep(TimeSpan.FromSeconds(0.9)); + int i_local = i; + tasks[i] = System.Threading.Tasks.Task.Run(() => LaunchAndComplete(i_local, () => BuildEngine7.ReleaseCores(1))); } + System.Threading.Tasks.Task.WhenAll(tasks).Wait(); + + BuildEngine7.Reacquire(); + return !Log.HasLoggedErrors; } + + void LaunchAndComplete(int i, Action completionCallback) + { + BuildEngine7.BlockingWaitForCore(); + Log.LogMessageFromText($"Action {i} started from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + Thread.Sleep(2_000); + Log.LogMessageFromText($"Action {i} completed from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + + completionCallback.Invoke(); + } } } From 9db29bbbd16858eeb36e5d5e4886e28eb886b5b4 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 15 Jul 2020 16:13:38 -0500 Subject: [PATCH 028/105] Treat resources as a separate pool; don't auto-acquire for tasks --- src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index 2a9dfd95217..f3709e7976a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -813,9 +813,7 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta using (FullTracking.Track(taskLoggingContext.TargetLoggingContext.Target.Name, _taskNode.Name, _buildRequestEntry.ProjectRootDirectory, _buildRequestEntry.RequestConfiguration.Project.PropertiesToBuildWith)) #endif { - taskHost?.RequireCores(1); taskResult = taskExecutionHost.Execute(); - taskHost?.ReleaseCores(1); } } } From e1c3bff21d7a05505fe26aaf1aedda27db1bc55b Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 15 Jul 2020 16:14:00 -0500 Subject: [PATCH 029/105] fixup! Add BlockingWaitForCore --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 1 + src/Tasks/SemaphoreCPUTask.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 44fa9699dcc..90b1f647afa 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -720,6 +720,7 @@ public void BlockingWaitForCore() var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; rms.RequireCores(1); + runningTotal += 1; } internal void ReleaseAllCores() diff --git a/src/Tasks/SemaphoreCPUTask.cs b/src/Tasks/SemaphoreCPUTask.cs index 177e2c07fce..7c8ca923dbd 100644 --- a/src/Tasks/SemaphoreCPUTask.cs +++ b/src/Tasks/SemaphoreCPUTask.cs @@ -3,6 +3,7 @@ using Microsoft.Build.Utilities; using System; +using System.Diagnostics; using System.Threading; namespace Microsoft.Build.Tasks @@ -13,6 +14,8 @@ class SemaphoreCPUTask : Task public override bool Execute() { + Log.LogMessageFromText($"Starting in {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + BuildEngine7.Yield(); //int initial = BuildEngine7.RequestCores(3123890); @@ -56,10 +59,12 @@ public override bool Execute() void LaunchAndComplete(int i, Action completionCallback) { + Stopwatch s = new Stopwatch(); + s.Start(); BuildEngine7.BlockingWaitForCore(); - Log.LogMessageFromText($"Action {i} started from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + Log.LogMessageFromText($"Action {i} started from {System.Diagnostics.Process.GetCurrentProcess().Id}, waited {s.Elapsed}", Framework.MessageImportance.High); Thread.Sleep(2_000); - Log.LogMessageFromText($"Action {i} completed from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); + Log.LogMessageFromText($"Action {i} completed from {System.Diagnostics.Process.GetCurrentProcess().Id}, total {s.Elapsed}", Framework.MessageImportance.High); completionCallback.Invoke(); } From 58fa35545104cf39901b80a1c5d7b596d5f55622 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 15 Jul 2020 16:35:35 -0500 Subject: [PATCH 030/105] Doc updates --- documentation/specs/resource-management.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/documentation/specs/resource-management.md b/documentation/specs/resource-management.md index 26ce5aac954..36c4ec8be1e 100644 --- a/documentation/specs/resource-management.md +++ b/documentation/specs/resource-management.md @@ -10,32 +10,28 @@ As a result, the standard guidance is to use only one multiproc option: MSBuild' ## Design -`IBuildEngine` will be extended to allow a task to indicate to MSBuild that it would like to consume more than one CPU core (`RequestCores`). These will be advisory only—a task can still do as much work as it desires with as many threads and processes as it desires. +`IBuildEngine` will be extended to allow a task to indicate to MSBuild that it would like to consume more than one CPU core (`BlockingWaitForCore`). These will be advisory only—a task can still do as much work as it desires with as many threads and processes as it desires. A cooperating task would limit its own parallelism to the number of CPU cores MSBuild can reserve for the requesting task. -All resources acquired by a task will be automatically returned when the task's `Execute()` method returns, but a task can optionally return a subset by calling `ReleaseCores`. +All resources acquired by a task will be automatically returned when the task's `Execute()` method returns, and a task can optionally return a subset by calling `ReleaseCores`. -MSBuild will respect core reservations given to tasks for task execution only. If a project/task is eligible for execution but has not yet started when other tasks consume all resources, MSBuild will wait until a resource is freed before starting execution of the new task. This is required to allow tasks in multiprocess builds to acquire resources that are allocated to worker nodes that are currently blocked on references to other projects. +MSBuild will respect core reservations given to tasks for tasks that opt into resource management only. If a project/task is eligible for execution, MSBuild will not wait until a resource is freed before starting execution of the new task. As a result, the machine can be oversubscribed, but only by a finite amount: the resource pool's core count. -When a task `Yield()`s, it releases 1 core reservation. When a task requests that the engine build another project it does likewise. Otherwise there could be a deadlock: a task is started (requiring the default 1 core), then yields/builds another project, then that tries to start a task (requiring the default 1 core), but that resource is held by the now-yielded task. +Task `Yield()`ing has no effect on the resources held by a task. ## Example -In a 16-process build of a solution with 30 projects, 16 worker nodes are launched and begin executing work. Most block on dependencies to projects `A`, `B`, `C`, `D`, and `E`, releasing their resource reservation. +In a 16-process build of a solution with 30 projects, 16 worker nodes are launched and begin executing work. Most block on dependencies to projects `A`, `B`, `C`, `D`, and `E`, so they don't have tasks running holding resources. Task `Work` is called in project `A` with 25 inputs. It would like to run as many as possible in parallel. It calls +TODO: what's the best calling pattern here? the thread thing I hacked up in the sample task seems bad. + ```C# int allowedParallelism = BuildEngine7.RequestCores(Inputs.Count); // Inputs.Count == 25 ``` -MSBuild is busy building tasks in projects `B`, `C`, `D`, and `E`, but many nodes are blocked on references to `A`, `B`, `C`, `D`, and `E`, so it is actively building 4 other tasks. As a result, the engine can dedicate 12 cores to `Work`. The call to `RequestCores` returns `11` (total possible parallelism `16` - other running tasks `4` - default reservation for the current task `1`). - -`Work` can then do 12 cores worth of computation. - -While `Work` has the 12-core reservation, another project completes a task execution and calls a new task `Work2`. This task calls `RequestCores`, but MSBuild has no spare allocation and returns `0`, causing `Work2` to begin its computation serially. - When `Work` returns, MSBuild automatically returns all resources reserved by the task to the pool. Before moving on with its processing, `Work2` calls `RequestCores` again, and this time receives a larger reservation. ## Implementation From 91fe07ab1ca40241ccd964dee4474d28b8c1dad3 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 16 Sep 2020 10:16:43 -0500 Subject: [PATCH 031/105] Block for at least one core in RequestCores --- src/Shared/UnitTests/MockEngine.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index 4e44324afb0..787fa88c1f5 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -495,8 +495,11 @@ public int RequestCores(int requestedCores) cpuCount ??= Semaphore.OpenExisting("cpuCount"); int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; + + cpuCount.WaitOne(); + // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. - for (int i = 0; i < requestedCores; i++) + for (int i = 1; i < requestedCores; i++) { if (cpuCount.WaitOne(0)) { From ba2db9aa3a137b93fa2109da9c3586fad2e12e0f Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 21 Oct 2020 13:31:34 -0500 Subject: [PATCH 032/105] Remove BlockingWaitForCore() since it's now redundant --- .../net/Microsoft.Build.Framework.cs | 1 - .../netstandard/Microsoft.Build.Framework.cs | 1 - src/Framework/IBuildEngine7.cs | 6 ------ src/Tasks/SemaphoreCPUTask.cs | 2 +- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs index 54373593d2b..935d146d443 100644 --- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs @@ -210,7 +210,6 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 { bool AllowFailureWithoutError { get; set; } - void BlockingWaitForCore(); void ReleaseCores(int coresToRelease); int RequestCores(int requestedCores); } diff --git a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs index 693b593d9c9..b77ffbb111d 100644 --- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs @@ -210,7 +210,6 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 { bool AllowFailureWithoutError { get; set; } - void BlockingWaitForCore(); void ReleaseCores(int coresToRelease); int RequestCores(int requestedCores); } diff --git a/src/Framework/IBuildEngine7.cs b/src/Framework/IBuildEngine7.cs index cb7bc8688d2..ca82448e707 100644 --- a/src/Framework/IBuildEngine7.cs +++ b/src/Framework/IBuildEngine7.cs @@ -24,11 +24,5 @@ public interface IBuildEngine7 : IBuildEngine6 /// /// Number of cores no longer in use. void ReleaseCores(int coresToRelease); - - /// - /// Block until a "core" from the managed pool for this build is available. - /// - void BlockingWaitForCore(); - } } diff --git a/src/Tasks/SemaphoreCPUTask.cs b/src/Tasks/SemaphoreCPUTask.cs index 7c8ca923dbd..9b2cd9cc63d 100644 --- a/src/Tasks/SemaphoreCPUTask.cs +++ b/src/Tasks/SemaphoreCPUTask.cs @@ -61,7 +61,7 @@ void LaunchAndComplete(int i, Action completionCallback) { Stopwatch s = new Stopwatch(); s.Start(); - BuildEngine7.BlockingWaitForCore(); + BuildEngine7.RequestCores(1); Log.LogMessageFromText($"Action {i} started from {System.Diagnostics.Process.GetCurrentProcess().Id}, waited {s.Elapsed}", Framework.MessageImportance.High); Thread.Sleep(2_000); Log.LogMessageFromText($"Action {i} completed from {System.Diagnostics.Process.GetCurrentProcess().Id}, total {s.Elapsed}", Framework.MessageImportance.High); From cc60df991d3a6560f8c282a094f76bca5c566fa4 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 21 Oct 2020 13:59:22 -0500 Subject: [PATCH 033/105] fixup! Block for at least one core in RequestCores --- .../Components/ResourceManager/ResourceManagerService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index e9d38447506..eec2d265530 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -78,8 +78,11 @@ public int RequestCores(int requestedCores) int i = 0; + // First core gets a blocking wait: the user task wants to do *something* + s.WaitOne(); + // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. - for (i = 0; i < requestedCores; i++) + for (i = 1; i < requestedCores; i++) { if (!s.WaitOne(0)) { From 0ab04815b65684ac5758b050a507255c92bae011 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 21 Oct 2020 14:11:12 -0500 Subject: [PATCH 034/105] Move to IBuildEngine8 since 7 shipped already --- .../net/Microsoft.Build.Framework.cs | 3 +++ .../netstandard/Microsoft.Build.Framework.cs | 3 +++ .../net/Microsoft.Build.Utilities.Core.cs | 1 + .../Microsoft.Build.Utilities.Core.cs | 1 + .../BackEnd/Components/IBuildComponentHost.cs | 2 +- .../Components/RequestBuilder/TaskHost.cs | 15 +++++++---- .../ResourceManager/ResourceManagerService.cs | 6 ----- src/Framework/IBuildEngine7.cs | 14 ---------- src/Framework/IBuildEngine8.cs | 26 +++++++++++++++++++ src/MSBuild/OutOfProcTaskHostNode.cs | 2 +- src/Shared/UnitTests/MockEngine.cs | 2 +- src/Tasks/SemaphoreCPUTask.cs | 8 +++--- src/Utilities/Task.cs | 10 +++++-- 13 files changed, 59 insertions(+), 34 deletions(-) create mode 100644 src/Framework/IBuildEngine8.cs diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs index 935d146d443..9e8211cd3dc 100644 --- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs @@ -210,6 +210,9 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 { bool AllowFailureWithoutError { get; set; } + } + public partial interface IBuildEngine8 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7 + { void ReleaseCores(int coresToRelease); int RequestCores(int requestedCores); } diff --git a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs index b77ffbb111d..ea0615e7872 100644 --- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs @@ -210,6 +210,9 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 { bool AllowFailureWithoutError { get; set; } + } + public partial interface IBuildEngine8 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7 + { void ReleaseCores(int coresToRelease); int RequestCores(int requestedCores); } diff --git a/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs b/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs index 40abd53b294..56a49e69207 100644 --- a/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs +++ b/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs @@ -353,6 +353,7 @@ public abstract partial class Task : Microsoft.Build.Framework.ITask public Microsoft.Build.Framework.IBuildEngine5 BuildEngine5 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine6 BuildEngine6 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine7 BuildEngine7 { get { throw null; } } + public Microsoft.Build.Framework.IBuildEngine8 BuildEngine8 { get { throw null; } } protected string HelpKeywordPrefix { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskHost HostObject { get { throw null; } set { } } public Microsoft.Build.Utilities.TaskLoggingHelper Log { get { throw null; } } diff --git a/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs b/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs index e6cc6f3fa50..ae04054e92b 100644 --- a/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs +++ b/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs @@ -198,6 +198,7 @@ public abstract partial class Task : Microsoft.Build.Framework.ITask public Microsoft.Build.Framework.IBuildEngine5 BuildEngine5 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine6 BuildEngine6 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine7 BuildEngine7 { get { throw null; } } + public Microsoft.Build.Framework.IBuildEngine8 BuildEngine8 { get { throw null; } } protected string HelpKeywordPrefix { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskHost HostObject { get { throw null; } set { } } public Microsoft.Build.Utilities.TaskLoggingHelper Log { get { throw null; } } diff --git a/src/Build/BackEnd/Components/IBuildComponentHost.cs b/src/Build/BackEnd/Components/IBuildComponentHost.cs index d3a5ddd6253..da27a3049dc 100644 --- a/src/Build/BackEnd/Components/IBuildComponentHost.cs +++ b/src/Build/BackEnd/Components/IBuildComponentHost.cs @@ -130,7 +130,7 @@ internal enum BuildComponentType SdkResolverService, /// - /// Resource manager for tasks to use via . + /// Resource manager for tasks to use via . /// TaskResourceManager, } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 1e68e12e9f0..8b6f8f87eec 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -34,7 +34,7 @@ internal class TaskHost : #if FEATURE_APPDOMAIN MarshalByRefObject, #endif - IBuildEngine7 + IBuildEngine8 { /// /// True if the "secret" environment variable MSBUILDNOINPROCNODE is set. @@ -677,6 +677,15 @@ public void LogTelemetry(string eventName, IDictionary propertie #region IBuildEngine7 Members + /// + /// Enables or disables emitting a default error when a task fails without logging errors + /// + public bool AllowFailureWithoutError { get; set; } = false; + + #endregion + + #region IBuildEngine8 Members + int runningTotal = 0; public int RequestCores(int requestedCores) @@ -729,10 +738,6 @@ internal void ReleaseAllCores() runningTotal = 0; } - /// - /// Enables or disables emitting a default error when a task fails without logging errors - /// - public bool AllowFailureWithoutError { get; set; } = false; #endregion /// diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index eec2d265530..72ba752baed 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -2,13 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Build.Shared; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; #nullable enable diff --git a/src/Framework/IBuildEngine7.cs b/src/Framework/IBuildEngine7.cs index ca82448e707..7f9200da1ab 100644 --- a/src/Framework/IBuildEngine7.cs +++ b/src/Framework/IBuildEngine7.cs @@ -10,19 +10,5 @@ namespace Microsoft.Build.Framework public interface IBuildEngine7 : IBuildEngine6 { public bool AllowFailureWithoutError { get; set; } - - /// - /// If a task launches multiple parallel processes, it should ask how many cores it can use. - /// - /// The number of cores a task can potentially use. - /// The number of cores a task is allowed to use. - int RequestCores(int requestedCores); - - /// - /// A task should notify the build manager when all or some of the requested cores are not used anymore. - /// When task is finished, the cores it requested are automatically released. - /// - /// Number of cores no longer in use. - void ReleaseCores(int coresToRelease); } } diff --git a/src/Framework/IBuildEngine8.cs b/src/Framework/IBuildEngine8.cs new file mode 100644 index 00000000000..dea08af8fdd --- /dev/null +++ b/src/Framework/IBuildEngine8.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Build.Framework +{ + /// + /// This interface extends to allow tasks to set whether they want to + /// log an error when a task returns without logging an error. + /// + public interface IBuildEngine8 : IBuildEngine7 + { + /// + /// If a task launches multiple parallel processes, it should ask how many cores it can use. + /// + /// The number of cores a task can potentially use. + /// The number of cores a task is allowed to use. + int RequestCores(int requestedCores); + + /// + /// A task should notify the build manager when all or some of the requested cores are not used anymore. + /// When task is finished, the cores it requested are automatically released. + /// + /// Number of cores no longer in use. + void ReleaseCores(int coresToRelease); + } +} diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index d3f232f3399..bebd72ec86d 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -33,7 +33,7 @@ internal class OutOfProcTaskHostNode : #if CLR2COMPATIBILITY IBuildEngine3 #else - IBuildEngine7 + IBuildEngine8 #endif { /// diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index 67b485c4a25..c9b5ad61fe8 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -31,7 +31,7 @@ namespace Microsoft.Build.UnitTests * is somewhat of a no-no for task assemblies. * **************************************************************************/ - internal sealed class MockEngine : IBuildEngine7 + internal sealed class MockEngine : IBuildEngine8 { private readonly object _lockObj = new object(); // Protects _log, _output private readonly ITestOutputHelper _output; diff --git a/src/Tasks/SemaphoreCPUTask.cs b/src/Tasks/SemaphoreCPUTask.cs index 9b2cd9cc63d..7bf57804326 100644 --- a/src/Tasks/SemaphoreCPUTask.cs +++ b/src/Tasks/SemaphoreCPUTask.cs @@ -16,7 +16,7 @@ public override bool Execute() { Log.LogMessageFromText($"Starting in {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); - BuildEngine7.Yield(); + BuildEngine8.Yield(); //int initial = BuildEngine7.RequestCores(3123890); //Log.LogMessageFromText($"Got {initial} cores from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); @@ -47,12 +47,12 @@ public override bool Execute() for (int i = 0; i < Repetitions; i++) { int i_local = i; - tasks[i] = System.Threading.Tasks.Task.Run(() => LaunchAndComplete(i_local, () => BuildEngine7.ReleaseCores(1))); + tasks[i] = System.Threading.Tasks.Task.Run(() => LaunchAndComplete(i_local, () => BuildEngine8.ReleaseCores(1))); } System.Threading.Tasks.Task.WhenAll(tasks).Wait(); - BuildEngine7.Reacquire(); + BuildEngine8.Reacquire(); return !Log.HasLoggedErrors; } @@ -61,7 +61,7 @@ void LaunchAndComplete(int i, Action completionCallback) { Stopwatch s = new Stopwatch(); s.Start(); - BuildEngine7.RequestCores(1); + BuildEngine8.RequestCores(1); Log.LogMessageFromText($"Action {i} started from {System.Diagnostics.Process.GetCurrentProcess().Id}, waited {s.Elapsed}", Framework.MessageImportance.High); Thread.Sleep(2_000); Log.LogMessageFromText($"Action {i} completed from {System.Diagnostics.Process.GetCurrentProcess().Id}, total {s.Elapsed}", Framework.MessageImportance.High); diff --git a/src/Utilities/Task.cs b/src/Utilities/Task.cs index 39846721d3c..db751b30d91 100644 --- a/src/Utilities/Task.cs +++ b/src/Utilities/Task.cs @@ -56,7 +56,7 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// The build engine interface available to tasks. public IBuildEngine BuildEngine { get; set; } - // The casts below are always possible because this class is built against the + // The casts below are always possible because this class is built against the // Orcas Framework assembly or later, so the version of MSBuild that does not // know about IBuildEngine2 will never load it. // No setters needed; the Engine always sets through the BuildEngine setter @@ -94,6 +94,12 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// public IBuildEngine7 BuildEngine7 => (IBuildEngine7)BuildEngine; + /// + /// Retrieves the version of the build engine interface provided by the host. + /// + public IBuildEngine8 BuildEngine8 => (IBuildEngine8)BuildEngine; + + /// /// The build engine sets this property if the host IDE has associated a host object with this particular task. /// @@ -103,7 +109,7 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// /// Gets an instance of a TaskLoggingHelper class containing task logging methods. /// The taskLoggingHelper is a MarshallByRef object which needs to have MarkAsInactive called - /// if the parent task is making the appdomain and marshaling this object into it. If the appdomain is not unloaded at the end of + /// if the parent task is making the appdomain and marshaling this object into it. If the appdomain is not unloaded at the end of /// the task execution and the MarkAsInactive method is not called this will result in a leak of the task instances in the appdomain the task was created within. /// /// The logging helper object. From f1e26c3c7fb313ce0bc6b5d4db42010f9249df73 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 3 Nov 2020 13:09:50 -0600 Subject: [PATCH 035/105] remove test-project.proj --- test-project.proj | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 test-project.proj diff --git a/test-project.proj b/test-project.proj deleted file mode 100644 index 3f46eb56486..00000000000 --- a/test-project.proj +++ /dev/null @@ -1,14 +0,0 @@ - - -

-

- - - - - - - - - - \ No newline at end of file From dfced2f76d61fee2790ecdb78456f20da18c6bbb Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 3 Nov 2020 13:14:44 -0600 Subject: [PATCH 036/105] Remove SemaphoreCPUTask --- src/Tasks/Microsoft.Build.Tasks.csproj | 3 +- src/Tasks/Microsoft.Common.tasks | 1 - src/Tasks/SemaphoreCPUTask.cs | 72 -------------------------- 3 files changed, 1 insertion(+), 75 deletions(-) delete mode 100644 src/Tasks/SemaphoreCPUTask.cs diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index e30f6b12dd1..f34423f982a 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -81,7 +81,7 @@ NGen.cs - + IInternable.cs @@ -519,7 +519,6 @@ - true diff --git a/src/Tasks/Microsoft.Common.tasks b/src/Tasks/Microsoft.Common.tasks index ad52912ef23..9f7f4620cd2 100644 --- a/src/Tasks/Microsoft.Common.tasks +++ b/src/Tasks/Microsoft.Common.tasks @@ -135,7 +135,6 @@ - diff --git a/src/Tasks/SemaphoreCPUTask.cs b/src/Tasks/SemaphoreCPUTask.cs deleted file mode 100644 index 7bf57804326..00000000000 --- a/src/Tasks/SemaphoreCPUTask.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.Build.Utilities; -using System; -using System.Diagnostics; -using System.Threading; - -namespace Microsoft.Build.Tasks -{ - class SemaphoreCPUTask : Task - { - private const int Repetitions = 20; - - public override bool Execute() - { - Log.LogMessageFromText($"Starting in {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); - - BuildEngine8.Yield(); - - //int initial = BuildEngine7.RequestCores(3123890); - //Log.LogMessageFromText($"Got {initial} cores from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); - - //if (initial > 0) - //{ - // while (initial > 0) - // { - // Thread.Sleep(TimeSpan.FromSeconds(1)); - // BuildEngine7.ReleaseCores(1); - // initial--; - // Log.LogMessageFromText($"Released 1 core from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); - // } - - // return !Log.HasLoggedErrors; - //} - - //for (int i = 0; i < 20; i++) - //{ - // int v = BuildEngine7.RequestCores(9999); - // Log.LogMessageFromText($"Got {v} cores from {System.Diagnostics.Process.GetCurrentProcess().Id}", Framework.MessageImportance.High); - // BuildEngine7.ReleaseCores(v + 20); - // Thread.Sleep(TimeSpan.FromSeconds(0.9)); - //} - - System.Threading.Tasks.Task[] tasks = new System.Threading.Tasks.Task[Repetitions]; - - for (int i = 0; i < Repetitions; i++) - { - int i_local = i; - tasks[i] = System.Threading.Tasks.Task.Run(() => LaunchAndComplete(i_local, () => BuildEngine8.ReleaseCores(1))); - } - - System.Threading.Tasks.Task.WhenAll(tasks).Wait(); - - BuildEngine8.Reacquire(); - - return !Log.HasLoggedErrors; - } - - void LaunchAndComplete(int i, Action completionCallback) - { - Stopwatch s = new Stopwatch(); - s.Start(); - BuildEngine8.RequestCores(1); - Log.LogMessageFromText($"Action {i} started from {System.Diagnostics.Process.GetCurrentProcess().Id}, waited {s.Elapsed}", Framework.MessageImportance.High); - Thread.Sleep(2_000); - Log.LogMessageFromText($"Action {i} completed from {System.Diagnostics.Process.GetCurrentProcess().Id}, total {s.Elapsed}", Framework.MessageImportance.High); - - completionCallback.Invoke(); - } - } -} From ed6a404ac708b96f037e8144da598d57d2bef1d9 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 3 Nov 2020 13:16:03 -0600 Subject: [PATCH 037/105] ?? --- src/MSBuild/XMake.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 6790805eaee..6376e0d3b8c 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -41,7 +41,6 @@ using LoggerDescription = Microsoft.Build.Logging.LoggerDescription; using ForwardingLoggerRecord = Microsoft.Build.Logging.ForwardingLoggerRecord; using BinaryLogger = Microsoft.Build.Logging.BinaryLogger; -using System.CodeDom; namespace Microsoft.Build.CommandLine { @@ -1198,7 +1197,6 @@ string outputResultsCache #if MSBUILDENABLEVSPROFILING DataCollection.CommentMarkProfile(8800, "Pending Build Request from MSBuild.exe"); #endif - BuildResultCode? result = null; var messagesToLogInBuildLoggers = Traits.Instance.EscapeHatches.DoNotSendDeferredMessagesToBuildManager @@ -1231,7 +1229,6 @@ string outputResultsCache else { buildRequest = new BuildRequestData(projectFile, globalProperties, toolsVersion, targets, null); - } } From 45458583817aaea0662ca4f14eed3f5e8824aea1 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Nov 2020 10:44:37 -0600 Subject: [PATCH 038/105] sort usings in MockHost --- src/Build.UnitTests/BackEnd/MockHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build.UnitTests/BackEnd/MockHost.cs b/src/Build.UnitTests/BackEnd/MockHost.cs index 7b06d339d63..48e0c18d755 100644 --- a/src/Build.UnitTests/BackEnd/MockHost.cs +++ b/src/Build.UnitTests/BackEnd/MockHost.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Build.BackEnd; +using Microsoft.Build.BackEnd.Components.ResourceManager; using Microsoft.Build.BackEnd.Logging; using System; using Microsoft.Build.BackEnd.SdkResolution; @@ -9,7 +10,6 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using LegacyThreadingData = Microsoft.Build.Execution.LegacyThreadingData; -using Microsoft.Build.BackEnd.Components.ResourceManager; namespace Microsoft.Build.UnitTests.BackEnd { From 3de11c0011be59cbcf259fc420bf12a99a6b6275 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Nov 2020 10:45:19 -0600 Subject: [PATCH 039/105] fixup! Treat resources as a separate pool; don't auto-acquire for tasks --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 8b6f8f87eec..9f4ada7a562 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -347,8 +347,6 @@ public void Yield() { lock (_callbackMonitor) { - ReleaseCores(1); - IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId == -1, "Cannot call Yield() while yielding."); _yieldThreadId = Thread.CurrentThread.ManagedThreadId; @@ -372,8 +370,6 @@ public void Reacquire() MSBuildEventSource.Log.ExecuteTaskYieldStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); MSBuildEventSource.Log.ExecuteTaskReacquireStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); - //TODO: should this RequireCores(1)? - builderCallback.Reacquire(); MSBuildEventSource.Log.ExecuteTaskReacquireStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); _yieldThreadId = -1; From 608bed76c0d17a26f011a104e2037656b8231798 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Nov 2020 10:47:16 -0600 Subject: [PATCH 040/105] fixup! Remove BlockingWaitForCore() since it's now redundant --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 9f4ada7a562..929cb4c5dc7 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -719,14 +719,6 @@ public void ReleaseCores(int coresToRelease) } } - public void BlockingWaitForCore() - { - var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; - - rms.RequireCores(1); - runningTotal += 1; - } - internal void ReleaseAllCores() { ReleaseCores(runningTotal); From 7a3da6b3b9b97a67c574f0767da1ad8bab291f2f Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Nov 2020 10:48:13 -0600 Subject: [PATCH 041/105] fixup! Treat resources as a separate pool; don't auto-acquire for tasks --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 929cb4c5dc7..3d413b9925e 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -909,8 +909,6 @@ private async Task BuildProjectFilesInParallelAsync(string[] List> targetOutputsPerProject = null; - ReleaseCores(1); // TODO: all? - #if FEATURE_FILE_TRACKER using (FullTracking.Suspend()) #endif From d87903f113591330ff210e1317ec01eba2b35da4 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Nov 2020 10:58:36 -0600 Subject: [PATCH 042/105] Remove RequireCores --- .../Components/RequestBuilder/TaskHost.cs | 9 --------- .../ResourceManager/ResourceManagerService.cs | 19 ------------------- 2 files changed, 28 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 3d413b9925e..fd048a8c16a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -697,15 +697,6 @@ public int RequestCores(int requestedCores) return coresAcquired; } - public void RequireCores(int requestedCores) - { - var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; - - rms.RequireCores(requestedCores); - - runningTotal += requestedCores; // default reservation - } - public void ReleaseCores(int coresToRelease) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 72ba752baed..8d2d718b84f 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -102,25 +102,6 @@ public void ReleaseCores(int coresToRelease) #if DEBUG TotalNumberHeld -= coresToRelease; -#endif - } - - internal void RequireCores(int requestedCores) - { - if (s is null) - { - // TODO: ErrorUtilities should be annotated so this can just be `ErrorUtilities.VerifyThrow` - // https://github.com/microsoft/msbuild/issues/5163 - throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); - } - - if (!s.WaitOne()) - { - ErrorUtilities.ThrowInternalError("Couldn't get a core to run a task even with infinite timeout"); - } - -#if DEBUG - TotalNumberHeld++; #endif } } From 9a83f35275b6e03160b7e994a4d8e28c160f7746 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Nov 2020 10:59:27 -0600 Subject: [PATCH 043/105] Better non-Windows behavior Assert TODOs were invalid because it's no longer a simple check --- .../ResourceManager/ResourceManagerService.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 8d2d718b84f..08b13925ebe 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -62,11 +62,12 @@ public int RequestCores(int requestedCores) { if (!NativeMethodsShared.IsWindows) { - return 0; + // Since the current implementation of the cross-process resource count uses + // named semaphores, it's not usable on non-Windows, so just return the + // guaranteed resource. + return 1; } - // TODO: ErrorUtilities should be annotated so this can just be `ErrorUtilities.VerifyThrow` - // https://github.com/microsoft/msbuild/issues/5163 throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); } @@ -91,8 +92,13 @@ public void ReleaseCores(int coresToRelease) { if (s is null) { - // TODO: ErrorUtilities should be annotated so this can just be `ErrorUtilities.VerifyThrow` - // https://github.com/microsoft/msbuild/issues/5163 + if (!NativeMethodsShared.IsWindows) + { + // Since the current implementation of the cross-process resource count uses + // named semaphores, it's not usable on non-Windows, so just continue. + return; + } + throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); } From 77344e23cdc282805ec867c9a00398e9a51720e4 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Nov 2020 11:00:05 -0600 Subject: [PATCH 044/105] fixup! Move to IBuildEngine8 since 7 shipped already --- src/Framework/IBuildEngine8.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Framework/IBuildEngine8.cs b/src/Framework/IBuildEngine8.cs index dea08af8fdd..5c73b072a75 100644 --- a/src/Framework/IBuildEngine8.cs +++ b/src/Framework/IBuildEngine8.cs @@ -4,7 +4,7 @@ namespace Microsoft.Build.Framework { ///

- /// This interface extends to allow tasks to set whether they want to + /// This interface extends to allow tasks to set whether they want to /// log an error when a task returns without logging an error. /// public interface IBuildEngine8 : IBuildEngine7 From 7fffd46c2d7b67595bdc6f71474c88d0c79448d9 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Thu, 5 Nov 2020 11:07:35 -0600 Subject: [PATCH 045/105] Generalize MockEngine semaphore --- src/Shared/UnitTests/MockEngine.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index c9b5ad61fe8..b51fb33a09a 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -33,6 +33,7 @@ namespace Microsoft.Build.UnitTests **************************************************************************/ internal sealed class MockEngine : IBuildEngine8 { + private readonly string ResourceSemaphoreName = $"MSBuildTestResourceSemaphore{Guid.NewGuid().ToString()}"; private readonly object _lockObj = new object(); // Protects _log, _output private readonly ITestOutputHelper _output; private readonly StringBuilder _log = new StringBuilder(); @@ -491,7 +492,7 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life Semaphore cpuCount; public int RequestCores(int requestedCores) { - cpuCount ??= Semaphore.OpenExisting("cpuCount"); + cpuCount ??= Semaphore.OpenExisting(ResourceSemaphoreName); int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; @@ -515,7 +516,7 @@ public int RequestCores(int requestedCores) public void ReleaseCores(int coresToRelease) { - cpuCount ??= Semaphore.OpenExisting("cpuCount"); + cpuCount ??= Semaphore.OpenExisting(ResourceSemaphoreName); coresToRelease = Math.Min(runningTotal, coresToRelease); @@ -528,7 +529,7 @@ public void ReleaseCores(int coresToRelease) public void BlockingWaitForCore() { - cpuCount ??= Semaphore.OpenExisting("cpuCount"); + cpuCount ??= Semaphore.OpenExisting(ResourceSemaphoreName); cpuCount.WaitOne(); } From 7d8b35924824208659c34cc3ae7fdb13dbc9e043 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Mon, 7 Dec 2020 13:56:57 -0600 Subject: [PATCH 046/105] Return nullable int to indicate whether resource management is possible --- .../net/Microsoft.Build.Framework.cs | 2 +- .../netstandard/Microsoft.Build.Framework.cs | 2 +- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 7 +++++-- .../Components/ResourceManager/ResourceManagerService.cs | 4 ++-- src/Framework/IBuildEngine8.cs | 2 +- src/MSBuild/OutOfProcTaskHostNode.cs | 7 ++++--- src/Shared/UnitTests/MockEngine.cs | 2 +- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs index 9e8211cd3dc..a997889edce 100644 --- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs @@ -214,7 +214,7 @@ public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, public partial interface IBuildEngine8 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7 { void ReleaseCores(int coresToRelease); - int RequestCores(int requestedCores); + System.Nullable RequestCores(int requestedCores); } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { diff --git a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs index ea0615e7872..c6f926b81f8 100644 --- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs @@ -214,7 +214,7 @@ public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, public partial interface IBuildEngine8 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7 { void ReleaseCores(int coresToRelease); - int RequestCores(int requestedCores); + System.Nullable RequestCores(int requestedCores); } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index fd048a8c16a..bd6fb1eef23 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -684,7 +684,7 @@ public void LogTelemetry(string eventName, IDictionary propertie int runningTotal = 0; - public int RequestCores(int requestedCores) + public int? RequestCores(int requestedCores) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; @@ -692,7 +692,10 @@ public int RequestCores(int requestedCores) var coresAcquired = rms.RequestCores(requestedCores); - runningTotal += coresAcquired; + if (coresAcquired.HasValue) + { + runningTotal += coresAcquired.Value; + } return coresAcquired; } diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 08b13925ebe..42e9ec47b73 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -56,7 +56,7 @@ public void ShutdownComponent() #endif } - public int RequestCores(int requestedCores) + public int? RequestCores(int requestedCores) { if (s is null) { @@ -65,7 +65,7 @@ public int RequestCores(int requestedCores) // Since the current implementation of the cross-process resource count uses // named semaphores, it's not usable on non-Windows, so just return the // guaranteed resource. - return 1; + return null; } throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); diff --git a/src/Framework/IBuildEngine8.cs b/src/Framework/IBuildEngine8.cs index 5c73b072a75..85e61dfc7c2 100644 --- a/src/Framework/IBuildEngine8.cs +++ b/src/Framework/IBuildEngine8.cs @@ -14,7 +14,7 @@ public interface IBuildEngine8 : IBuildEngine7 ///
/// The number of cores a task can potentially use. /// The number of cores a task is allowed to use. - int RequestCores(int requestedCores); + int? RequestCores(int requestedCores); /// /// A task should notify the build manager when all or some of the requested cores are not used anymore. diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index bebd72ec86d..9163337cd6c 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -1162,14 +1162,15 @@ private void LogErrorFromResource(string messageResource) LogErrorEvent(error); } - public int RequestCores(int requestedCores) + public int? RequestCores(int requestedCores) { - throw new NotImplementedException(); + // indicate to caller that resource management isn't hooked up + return null; } public void ReleaseCores(int coresToRelease) { - throw new NotImplementedException(); + // Do nothing: no resource management in OOP nodes } public void BlockingWaitForCore() diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index b51fb33a09a..5f39b7f7cd0 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -490,7 +490,7 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life int runningTotal = 0; Semaphore cpuCount; - public int RequestCores(int requestedCores) + public int? RequestCores(int requestedCores) { cpuCount ??= Semaphore.OpenExisting(ResourceSemaphoreName); From 82a25c4c40dbbd09ec535b264fe5f51c28208947 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Mon, 7 Dec 2020 13:57:26 -0600 Subject: [PATCH 047/105] fixup! Remove RequireCores --- src/MSBuild/OutOfProcTaskHostNode.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index 9163337cd6c..0b871be8676 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -1172,10 +1172,5 @@ public void ReleaseCores(int coresToRelease) { // Do nothing: no resource management in OOP nodes } - - public void BlockingWaitForCore() - { - throw new NotImplementedException(); - } } } From b8b52cca2eb769b5bbb6c09addcbe574ab573bef Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 20 Nov 2020 09:51:25 -0600 Subject: [PATCH 048/105] Nix whitespace-only changes in ProjectCollection --- src/Build/Definition/ProjectCollection.cs | 32 +++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Build/Definition/ProjectCollection.cs b/src/Build/Definition/ProjectCollection.cs index 3b4e9689ffa..5ef5fdfe6e8 100644 --- a/src/Build/Definition/ProjectCollection.cs +++ b/src/Build/Definition/ProjectCollection.cs @@ -593,7 +593,7 @@ public ICollection Loggers using (_locker.EnterUpgradeableReadLock()) { return _loggingService.Loggers == null - ? (ICollection)ReadOnlyEmptyCollection.Instance + ? (ICollection) ReadOnlyEmptyCollection.Instance : new List(_loggingService.Loggers); } } @@ -1070,7 +1070,7 @@ internal ICollection GetLoadedProjects(bool includeExternal, string ful List loaded; using (_locker.EnterWriteLock()) { - loaded = fullPath == null ? new List(_loadedProjects) : new List(_loadedProjects.GetMatchingProjectsIfAny(fullPath)); + loaded = fullPath == null ? new List(_loadedProjects) : new List(_loadedProjects.GetMatchingProjectsIfAny(fullPath)); } if (includeExternal) @@ -1131,7 +1131,7 @@ public Project LoadProject(string fileName, IDictionary globalPr { // We need to update the set of global properties to merge in the ProjectCollection global properties -- // otherwise we might end up declaring "not matching" a project that actually does ... and then throw - // an exception when we go to actually add the newly created project to the ProjectCollection. + // an exception when we go to actually add the newly created project to the ProjectCollection. // BUT remember that project global properties win -- don't override a property that already exists. foreach (KeyValuePair globalProperty in GlobalProperties) { @@ -1148,9 +1148,9 @@ public Project LoadProject(string fileName, IDictionary globalPr if (toolsVersion == null) { - // Load the project XML to get any ToolsVersion attribute. + // Load the project XML to get any ToolsVersion attribute. // If there isn't already an equivalent project loaded, the real load we'll do will be satisfied from the cache. - // If there is already an equivalent project loaded, we'll never need this XML -- but it'll already + // If there is already an equivalent project loaded, we'll never need this XML -- but it'll already // have been loaded by that project so it will have been satisfied from the ProjectRootElementCache. // Either way, no time wasted. try @@ -1327,7 +1327,7 @@ public void UnloadProject(Project project) // Aggressively release any strings from all the contributing documents. // It's fine if we cache less (by now we likely did a lot of loading and got the benefits) - // If we don't do this, we could be releasing the last reference to a + // If we don't do this, we could be releasing the last reference to a // ProjectRootElement, causing it to fall out of the weak cache leaving its strings and XML // behind in the string cache. project.Xml.XmlDocument.ClearAnyCachedStrings(); @@ -1475,7 +1475,7 @@ public void Dispose() GC.SuppressFinalize(this); } - #region IBuildComponent Members +#region IBuildComponent Members /// /// Initializes the component with the component host. @@ -1492,7 +1492,7 @@ void IBuildComponent.ShutdownComponent() { } - #endregion +#endregion /// /// Unloads a project XML root element from the cache entirely, if it is not @@ -1548,12 +1548,12 @@ internal void OnAfterRenameLoadedProject(string oldFullPathIfAny, Project projec ErrorUtilities.VerifyThrowInvalidOperation(existed, "OM_ProjectWasNotLoaded"); } - // The only time this ever gets called with a null full path is when the project is first being - // constructed. The mere fact that this method is being called means that this project will belong - // to this project collection. As such, it has already had all necessary global properties applied - // when being constructed -- we don't need to do anything special here. - // If we did add global properties here, we would just end up either duplicating work or possibly - // wiping out global properties set on the project meant to override the ProjectCollection copies. + // The only time this ever gets called with a null full path is when the project is first being + // constructed. The mere fact that this method is being called means that this project will belong + // to this project collection. As such, it has already had all necessary global properties applied + // when being constructed -- we don't need to do anything special here. + // If we did add global properties here, we would just end up either duplicating work or possibly + // wiping out global properties set on the project meant to override the ProjectCollection copies. _loadedProjects.AddProject(project); if (_hostServices != null) @@ -1908,7 +1908,7 @@ public ReusableLogger(ILogger originalLogger) _originalLogger = originalLogger; } - #region IEventSource Members +#region IEventSource Members /// /// The Message logging event @@ -2119,7 +2119,7 @@ public void Shutdown() } } - #endregion +#endregion /// /// Registers for all of the events on the specified event source. From f3e347ed9fe4e465f11c0eac19e3dbd940099c2c Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 20 Nov 2020 09:53:32 -0600 Subject: [PATCH 049/105] fixup! fixup! Remove BlockingWaitForCore() since it's now redundant --- src/MSBuild/OutOfProcTaskHostNode.cs | 190 +++++++++++++-------------- 1 file changed, 95 insertions(+), 95 deletions(-) diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index 0b871be8676..a4e1a08f878 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -27,7 +27,7 @@ namespace Microsoft.Build.CommandLine /// internal class OutOfProcTaskHostNode : #if FEATURE_APPDOMAIN - MarshalByRefObject, + MarshalByRefObject, #endif INodePacketFactory, INodePacketHandler, #if CLR2COMPATIBILITY @@ -38,27 +38,27 @@ internal class OutOfProcTaskHostNode : { /// /// Keeps a record of all environment variables that, on startup of the task host, have a different - /// value from those that are passed to the task host in the configuration packet for the first task. - /// These environments are assumed to be effectively identical, so the only difference between the - /// two sets of values should be any environment variables that differ between e.g. a 32-bit and a 64-bit - /// process. Those are the variables that this dictionary should store. - /// - /// - The key into the dictionary is the name of the environment variable. - /// - The Key of the KeyValuePair is the value of the variable in the parent process -- the value that we - /// wish to ensure is replaced by whatever the correct value in our current process is. - /// - The Value of the KeyValuePair is the value of the variable in the current process -- the value that - /// we wish to replay the Key value with in the environment that we receive from the parent before - /// applying it to the current process. - /// - /// Note that either value in the KeyValuePair can be null, as it is completely possible to have an - /// environment variable that is set in 32-bit processes but not in 64-bit, or vice versa. - /// - /// This dictionary must be static because otherwise, if a node is sitting around waiting for reuse, it will - /// have inherited the environment from the previous build, and any differences between the two will be seen - /// as "legitimate". There is no way for us to know what the differences between the startup environment of - /// the previous build and the environment of the first task run in the task host in this build -- so we - /// must assume that the 4ish system environment variables that this is really meant to catch haven't - /// somehow magically changed between two builds spaced no more than 15 minutes apart. + /// value from those that are passed to the task host in the configuration packet for the first task. + /// These environments are assumed to be effectively identical, so the only difference between the + /// two sets of values should be any environment variables that differ between e.g. a 32-bit and a 64-bit + /// process. Those are the variables that this dictionary should store. + /// + /// - The key into the dictionary is the name of the environment variable. + /// - The Key of the KeyValuePair is the value of the variable in the parent process -- the value that we + /// wish to ensure is replaced by whatever the correct value in our current process is. + /// - The Value of the KeyValuePair is the value of the variable in the current process -- the value that + /// we wish to replay the Key value with in the environment that we receive from the parent before + /// applying it to the current process. + /// + /// Note that either value in the KeyValuePair can be null, as it is completely possible to have an + /// environment variable that is set in 32-bit processes but not in 64-bit, or vice versa. + /// + /// This dictionary must be static because otherwise, if a node is sitting around waiting for reuse, it will + /// have inherited the environment from the previous build, and any differences between the two will be seen + /// as "legitimate". There is no way for us to know what the differences between the startup environment of + /// the previous build and the environment of the first task run in the task host in this build -- so we + /// must assume that the 4ish system environment variables that this is really meant to catch haven't + /// somehow magically changed between two builds spaced no more than 15 minutes apart. /// private static IDictionary> s_mismatchedEnvironmentValues; @@ -108,13 +108,13 @@ internal class OutOfProcTaskHostNode : private bool _isTaskExecuting; /// - /// The event which is set when a task has completed. + /// The event which is set when a task has completed. /// private AutoResetEvent _taskCompleteEvent; /// - /// Packet containing all the information relating to the - /// completed state of the task. + /// Packet containing all the information relating to the + /// completed state of the task. /// private TaskHostTaskComplete _taskCompletePacket; @@ -145,15 +145,15 @@ internal class OutOfProcTaskHostNode : private bool _debugCommunications; /// - /// Flag indicating whether we should modify the environment based on any differences we find between that of the - /// task host at startup and the environment passed to us in our initial task configuration packet. + /// Flag indicating whether we should modify the environment based on any differences we find between that of the + /// task host at startup and the environment passed to us in our initial task configuration packet. /// private bool _updateEnvironment; /// - /// An interim step between MSBuildTaskHostDoNotUpdateEnvironment=1 and the default update behavior: go ahead and - /// do all the updates that we would otherwise have done by default, but log any updates that are made (at low - /// importance) so that the user is aware. + /// An interim step between MSBuildTaskHostDoNotUpdateEnvironment=1 and the default update behavior: go ahead and + /// do all the updates that we would otherwise have done by default, but log any updates that are made (at low + /// importance) so that the user is aware. /// private bool _updateEnvironmentAndLog; @@ -169,9 +169,9 @@ internal class OutOfProcTaskHostNode : /// public OutOfProcTaskHostNode() { - // We don't know what the current build thinks this variable should be until RunTask(), but as a fallback in case there are + // We don't know what the current build thinks this variable should be until RunTask(), but as a fallback in case there are // communications before we get the configuration set up, just go with what was already in the environment from when this node - // was initially launched. + // was initially launched. _debugCommunications = (Environment.GetEnvironmentVariable("MSBUILDDEBUGCOMM") == "1"); _receivedPackets = new Queue(); @@ -194,7 +194,7 @@ public OutOfProcTaskHostNode() #region IBuildEngine Implementation (Properties) /// - /// Returns the value of ContinueOnError for the currently executing task. + /// Returns the value of ContinueOnError for the currently executing task. /// public bool ContinueOnError { @@ -206,7 +206,7 @@ public bool ContinueOnError } /// - /// Returns the line number of the location in the project file of the currently executing task. + /// Returns the line number of the location in the project file of the currently executing task. /// public int LineNumberOfTaskNode { @@ -218,7 +218,7 @@ public int LineNumberOfTaskNode } /// - /// Returns the column number of the location in the project file of the currently executing task. + /// Returns the column number of the location in the project file of the currently executing task. /// public int ColumnNumberOfTaskNode { @@ -230,7 +230,7 @@ public int ColumnNumberOfTaskNode } /// - /// Returns the project file of the currently executing task. + /// Returns the project file of the currently executing task. /// public string ProjectFileOfTaskNode { @@ -246,8 +246,8 @@ public string ProjectFileOfTaskNode #region IBuildEngine2 Implementation (Properties) /// - /// Stub implementation of IBuildEngine2.IsRunningMultipleNodes. The task host does not support this sort of - /// IBuildEngine callback, so error. + /// Stub implementation of IBuildEngine2.IsRunningMultipleNodes. The task host does not support this sort of + /// IBuildEngine callback, so error. /// public bool IsRunningMultipleNodes { @@ -270,9 +270,9 @@ public bool IsRunningMultipleNodes #region IBuildEngine Implementation (Methods) /// - /// Sends the provided error back to the parent node to be logged, tagging it with - /// the parent node's ID so that, as far as anyone is concerned, it might as well have - /// just come from the parent node to begin with. + /// Sends the provided error back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. /// public void LogErrorEvent(BuildErrorEventArgs e) { @@ -280,9 +280,9 @@ public void LogErrorEvent(BuildErrorEventArgs e) } /// - /// Sends the provided warning back to the parent node to be logged, tagging it with - /// the parent node's ID so that, as far as anyone is concerned, it might as well have - /// just come from the parent node to begin with. + /// Sends the provided warning back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. /// public void LogWarningEvent(BuildWarningEventArgs e) { @@ -290,9 +290,9 @@ public void LogWarningEvent(BuildWarningEventArgs e) } /// - /// Sends the provided message back to the parent node to be logged, tagging it with - /// the parent node's ID so that, as far as anyone is concerned, it might as well have - /// just come from the parent node to begin with. + /// Sends the provided message back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. /// public void LogMessageEvent(BuildMessageEventArgs e) { @@ -300,9 +300,9 @@ public void LogMessageEvent(BuildMessageEventArgs e) } /// - /// Sends the provided custom event back to the parent node to be logged, tagging it with - /// the parent node's ID so that, as far as anyone is concerned, it might as well have - /// just come from the parent node to begin with. + /// Sends the provided custom event back to the parent node to be logged, tagging it with + /// the parent node's ID so that, as far as anyone is concerned, it might as well have + /// just come from the parent node to begin with. /// public void LogCustomEvent(CustomBuildEventArgs e) { @@ -310,8 +310,8 @@ public void LogCustomEvent(CustomBuildEventArgs e) } /// - /// Stub implementation of IBuildEngine.BuildProjectFile. The task host does not support IBuildEngine - /// callbacks for the purposes of building projects, so error. + /// Stub implementation of IBuildEngine.BuildProjectFile. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. /// public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) { @@ -324,8 +324,8 @@ public bool BuildProjectFile(string projectFileName, string[] targetNames, IDict #region IBuildEngine2 Implementation (Methods) /// - /// Stub implementation of IBuildEngine2.BuildProjectFile. The task host does not support IBuildEngine - /// callbacks for the purposes of building projects, so error. + /// Stub implementation of IBuildEngine2.BuildProjectFile. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. /// public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) { @@ -334,8 +334,8 @@ public bool BuildProjectFile(string projectFileName, string[] targetNames, IDict } /// - /// Stub implementation of IBuildEngine2.BuildProjectFilesInParallel. The task host does not support IBuildEngine - /// callbacks for the purposes of building projects, so error. + /// Stub implementation of IBuildEngine2.BuildProjectFilesInParallel. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. /// public bool BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) { @@ -348,8 +348,8 @@ public bool BuildProjectFilesInParallel(string[] projectFileNames, string[] targ #region IBuildEngine3 Implementation /// - /// Stub implementation of IBuildEngine3.BuildProjectFilesInParallel. The task host does not support IBuildEngine - /// callbacks for the purposes of building projects, so error. + /// Stub implementation of IBuildEngine3.BuildProjectFilesInParallel. The task host does not support IBuildEngine + /// callbacks for the purposes of building projects, so error. /// public BuildEngineResult BuildProjectFilesInParallel(string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IList[] removeGlobalProperties, string[] toolsVersion, bool returnTargetOutputs) { @@ -359,7 +359,7 @@ public BuildEngineResult BuildProjectFilesInParallel(string[] projectFileNames, /// /// Stub implementation of IBuildEngine3.Yield. The task host does not support yielding, so just go ahead and silently - /// return, letting the task continue. + /// return, letting the task continue. /// public void Yield() { @@ -367,8 +367,8 @@ public void Yield() } /// - /// Stub implementation of IBuildEngine3.Reacquire. The task host does not support yielding, so just go ahead and silently - /// return, letting the task continue. + /// Stub implementation of IBuildEngine3.Reacquire. The task host does not support yielding, so just go ahead and silently + /// return, letting the task continue. /// public void Reacquire() { @@ -613,7 +613,7 @@ private void HandlePacket(INodePacket packet) } /// - /// Configure the task host according to the information received in the + /// Configure the task host according to the information received in the /// configuration packet /// private void HandleTaskHostConfiguration(TaskHostConfiguration taskHostConfiguration) @@ -649,10 +649,10 @@ private void CompleteTask() _currentConfiguration = null; - // If the task has been canceled, the event will still be set. - // If so, now that we've completed the task, we want to shut down - // this node -- with no reuse, since we don't know whether the - // task we canceled left the node in a good state or not. + // If the task has been canceled, the event will still be set. + // If so, now that we've completed the task, we want to shut down + // this node -- with no reuse, since we don't know whether the + // task we canceled left the node in a good state or not. if (_taskCancelledEvent.WaitOne(0)) { _shutdownReason = NodeEngineShutdownReason.BuildComplete; @@ -680,7 +680,7 @@ private void CancelTask() if (_isTaskExecuting) { #if FEATURE_THREAD_ABORT - // The thread will be terminated crudely so our environment may be trashed but it's ok since we are + // The thread will be terminated crudely so our environment may be trashed but it's ok since we are // shutting down ASAP. _taskRunnerThread.Abort(); #endif @@ -787,9 +787,9 @@ private void RunTask(object state) TaskHostConfiguration taskConfiguration = state as TaskHostConfiguration; IDictionary taskParams = taskConfiguration.TaskParameters; - // We only really know the values of these variables for sure once we see what we received from our parent - // environment -- otherwise if this was a completely new build, we could lose out on expected environment - // variables. + // We only really know the values of these variables for sure once we see what we received from our parent + // environment -- otherwise if this was a completely new build, we could lose out on expected environment + // variables. _debugCommunications = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBUILDDEBUGCOMM", "1", StringComparison.OrdinalIgnoreCase); _updateEnvironment = !taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostDoNotUpdateEnvironment", "1", StringComparison.OrdinalIgnoreCase); _updateEnvironmentAndLog = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostUpdateEnvironmentAndLog", "1", StringComparison.OrdinalIgnoreCase); @@ -903,9 +903,9 @@ private void RunTask(object state) } /// - /// Set the environment for the task host -- includes possibly munging the given - /// environment somewhat to account for expected environment differences between, - /// e.g. parent processes and task hosts of different bitnesses. + /// Set the environment for the task host -- includes possibly munging the given + /// environment somewhat to account for expected environment differences between, + /// e.g. parent processes and task hosts of different bitnesses. /// private void SetTaskHostEnvironment(IDictionary environment) { @@ -919,7 +919,7 @@ private void SetTaskHostEnvironment(IDictionary environment) string oldValue = s_mismatchedEnvironmentValues[variable].Key; string newValue = s_mismatchedEnvironmentValues[variable].Value; - // We don't check the return value, because having the variable not exist == be + // We don't check the return value, because having the variable not exist == be // null is perfectly valid, and mismatchedEnvironmentValues stores those values // as null as well, so the String.Equals should still return that they are equal. string environmentValue = null; @@ -954,8 +954,8 @@ private void SetTaskHostEnvironment(IDictionary environment) } } - // if it's still null here, there were no changes necessary -- so just - // set it to what was already passed in. + // if it's still null here, there were no changes necessary -- so just + // set it to what was already passed in. if (updatedEnvironment == null) { updatedEnvironment = environment; @@ -965,10 +965,10 @@ private void SetTaskHostEnvironment(IDictionary environment) } /// - /// Given the environment of the task host at the end of task execution, make sure that any - /// processor-specific variables have been re-applied in the correct form for the main node, - /// so that when we pass this dictionary back to the main node, all it should have to do - /// is just set it. + /// Given the environment of the task host at the end of task execution, make sure that any + /// processor-specific variables have been re-applied in the correct form for the main node, + /// so that when we pass this dictionary back to the main node, all it should have to do + /// is just set it. /// private IDictionary UpdateEnvironmentForMainNode(IDictionary environment) { @@ -979,14 +979,14 @@ private void SetTaskHostEnvironment(IDictionary environment) { foreach (string variable in s_mismatchedEnvironmentValues.Keys) { - // Since this is munging the property list for returning to the parent process, - // then the value we wish to replace is the one that is in this process, and the - // replacement value is the one that originally came from the parent process, + // Since this is munging the property list for returning to the parent process, + // then the value we wish to replace is the one that is in this process, and the + // replacement value is the one that originally came from the parent process, // instead of the other way around. string oldValue = s_mismatchedEnvironmentValues[variable].Value; string newValue = s_mismatchedEnvironmentValues[variable].Key; - // We don't check the return value, because having the variable not exist == be + // We don't check the return value, because having the variable not exist == be // null is perfectly valid, and mismatchedEnvironmentValues stores those values // as null as well, so the String.Equals should still return that they are equal. string environmentValue = null; @@ -1011,8 +1011,8 @@ private void SetTaskHostEnvironment(IDictionary environment) } } - // if it's still null here, there were no changes necessary -- so just - // set it to what was already passed in. + // if it's still null here, there were no changes necessary -- so just + // set it to what was already passed in. if (updatedEnvironment == null) { updatedEnvironment = environment; @@ -1022,20 +1022,20 @@ private void SetTaskHostEnvironment(IDictionary environment) } /// - /// Make sure the mismatchedEnvironmentValues table has been populated. Note that this should - /// only do actual work on the very first run of a task in the task host -- otherwise, it should - /// already have been populated. + /// Make sure the mismatchedEnvironmentValues table has been populated. Note that this should + /// only do actual work on the very first run of a task in the task host -- otherwise, it should + /// already have been populated. /// private void InitializeMismatchedEnvironmentTable(IDictionary environment) { if (s_mismatchedEnvironmentValues == null) { - // This is the first time that we have received a TaskHostConfiguration packet, so we - // need to construct the mismatched environment table based on our current environment + // This is the first time that we have received a TaskHostConfiguration packet, so we + // need to construct the mismatched environment table based on our current environment // (assumed to be effectively identical to startup) and the environment we were given - // via the task host configuration, assumed to be effectively identical to the startup + // via the task host configuration, assumed to be effectively identical to the startup // environment of the task host, given that the configuration packet is sent immediately - // after the node is launched. + // after the node is launched. s_mismatchedEnvironmentValues = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (string variable in _savedEnvironment.Keys) @@ -1075,7 +1075,7 @@ private void InitializeMismatchedEnvironmentTable(IDictionary en } /// - /// Sends the requested packet across to the main node. + /// Sends the requested packet across to the main node. /// private void SendBuildEvent(BuildEventArgs e) { @@ -1084,7 +1084,7 @@ private void SendBuildEvent(BuildEventArgs e) if (!e.GetType().GetTypeInfo().IsSerializable) { // log a warning and bail. This will end up re-calling SendBuildEvent, but we know for a fact - // that the warning that we constructed is serializable, so everything should be good. + // that the warning that we constructed is serializable, so everything should be good. LogWarningFromResource("ExpectedEventToBeSerializable", e.GetType().Name); return; } From ab384aa8061be8ec32446fbbc03dab658e390332 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 20 Nov 2020 09:54:49 -0600 Subject: [PATCH 050/105] Task whitespace fixes --- src/Utilities/Task.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Utilities/Task.cs b/src/Utilities/Task.cs index db751b30d91..a9f3de6ff36 100644 --- a/src/Utilities/Task.cs +++ b/src/Utilities/Task.cs @@ -56,7 +56,7 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// The build engine interface available to tasks. public IBuildEngine BuildEngine { get; set; } - // The casts below are always possible because this class is built against the + // The casts below are always possible because this class is built against the // Orcas Framework assembly or later, so the version of MSBuild that does not // know about IBuildEngine2 will never load it. // No setters needed; the Engine always sets through the BuildEngine setter @@ -99,7 +99,6 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// public IBuildEngine8 BuildEngine8 => (IBuildEngine8)BuildEngine; - /// /// The build engine sets this property if the host IDE has associated a host object with this particular task. /// @@ -109,7 +108,7 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// /// Gets an instance of a TaskLoggingHelper class containing task logging methods. /// The taskLoggingHelper is a MarshallByRef object which needs to have MarkAsInactive called - /// if the parent task is making the appdomain and marshaling this object into it. If the appdomain is not unloaded at the end of + /// if the parent task is making the appdomain and marshaling this object into it. If the appdomain is not unloaded at the end of /// the task execution and the MarkAsInactive method is not called this will result in a leak of the task instances in the appdomain the task was created within. /// /// The logging helper object. From a9d8061fc39792935f03aefe8f6df2fb049960a1 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 20 Nov 2020 10:05:31 -0600 Subject: [PATCH 051/105] Update doc for non-Windows behavior --- documentation/specs/resource-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/resource-management.md b/documentation/specs/resource-management.md index 36c4ec8be1e..76113f29fc3 100644 --- a/documentation/specs/resource-management.md +++ b/documentation/specs/resource-management.md @@ -38,4 +38,4 @@ When `Work` returns, MSBuild automatically returns all resources reserved by the The initial implementation of the system will use a Win32 [named semaphore](https://docs.microsoft.com/windows/win32/sync/semaphore-objects) to track resource use. This was the implementation of `MultiToolTask` in the VC++ toolchain and is a performant implementation of a counter that can be shared across processes. -On platforms where named semaphores are not supported (.NET Core MSBuild running on macOS, Linux, or other UNIXes), `RequestCores` will always return `0`. We will consider implementing full support using a cross-process semaphore (or an addition to the existing MSBuild communication protocol, if it isn't prohibitively costly to do the packet exchange and processing) on these platforms in the future. +On platforms where named semaphores are not supported (.NET Core MSBuild running on macOS, Linux, or other UNIXes), `RequestCores` will always return `1`. That is the minimum return value when the manager is fully functional, and hopefully will not dramatically overburden the machine. We will consider implementing full support using a cross-process semaphore (or an addition to the existing MSBuild communication protocol, if it isn't prohibitively costly to do the packet exchange and processing) on these platforms in the future. From 25ec10cfc904e4d1be92eda66a861a517658e304 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 20 Nov 2020 10:18:54 -0600 Subject: [PATCH 052/105] Update doc with better example, caveats --- documentation/specs/resource-management.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/documentation/specs/resource-management.md b/documentation/specs/resource-management.md index 76113f29fc3..626efb30d22 100644 --- a/documentation/specs/resource-management.md +++ b/documentation/specs/resource-management.md @@ -26,16 +26,20 @@ In a 16-process build of a solution with 30 projects, 16 worker nodes are launch Task `Work` is called in project `A` with 25 inputs. It would like to run as many as possible in parallel. It calls -TODO: what's the best calling pattern here? the thread thing I hacked up in the sample task seems bad. - ```C# -int allowedParallelism = BuildEngine7.RequestCores(Inputs.Count); // Inputs.Count == 25 +int allowedParallelism = BuildEngine8.RequestCores(Inputs.Count); // Inputs.Count == 25 ``` -When `Work` returns, MSBuild automatically returns all resources reserved by the task to the pool. Before moving on with its processing, `Work2` calls `RequestCores` again, and this time receives a larger reservation. +and gets `16`--the number of cores available to the build overall. Other tasks that do not call `RequestCores` do not affect this value. + +While `A` runs `Work`, projects `B` and `C` run another task `Work2` that also calls `RequestCores` with a high value. Since `Work` in `A` has reserved all cores, the calls in `B` and `C` block, waiting on `Work` to release cores (or return). + +When `Work` returns, MSBuild automatically returns all resources reserved by the task to the pool. At that time `Work2`'s calls to `RequestCores` unblock, and ## Implementation The initial implementation of the system will use a Win32 [named semaphore](https://docs.microsoft.com/windows/win32/sync/semaphore-objects) to track resource use. This was the implementation of `MultiToolTask` in the VC++ toolchain and is a performant implementation of a counter that can be shared across processes. +There is no guarantee of fair resource allocation. If multiple tasks are blocked in `RequestCores`, one of them will be unblocked when cores are released, but the returned cores may be split evenly, unevenly, or even entirely given to one task. + On platforms where named semaphores are not supported (.NET Core MSBuild running on macOS, Linux, or other UNIXes), `RequestCores` will always return `1`. That is the minimum return value when the manager is fully functional, and hopefully will not dramatically overburden the machine. We will consider implementing full support using a cross-process semaphore (or an addition to the existing MSBuild communication protocol, if it isn't prohibitively costly to do the packet exchange and processing) on these platforms in the future. From b3626006c5518c7fa9c6a9c7edd92ab70884dedf Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 20 Nov 2020 14:37:36 -0600 Subject: [PATCH 053/105] Whitespace fixes in TaskBuilder --- .../Components/RequestBuilder/TaskBuilder.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index c6de1e9f85f..5390d169443 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -340,7 +340,7 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo taskHost?.MarkAsInactive(); - // Now all task batches are done, apply all item adds to the outer + // Now all task batches are done, apply all item adds to the outer // target batch; we do this even if the task wasn't found (in that case, // no items or properties will have been added to the scope) if (buckets != null) @@ -402,14 +402,14 @@ private async Task ExecuteBucket(TaskHost taskHost, ItemBucket b { // Change to the project root directory. // If that directory does not exist, do nothing. (Do not check first as it is almost always there and it is slow) - // This is because if the project has not been saved, this directory may not exist, yet it is often useful to still be able to build the project. + // This is because if the project has not been saved, this directory may not exist, yet it is often useful to still be able to build the project. // No errors are masked by doing this: errors loading the project from disk are reported at load time, if necessary. NativeMethodsShared.SetCurrentDirectory(_buildRequestEntry.ProjectRootDirectory); } if (howToExecuteTask == TaskExecutionMode.ExecuteTaskAndGatherOutputs) { - // We need to find the task before logging the task started event so that the using task statement comes before the task started event + // We need to find the task before logging the task started event so that the using task statement comes before the task started event IDictionary taskIdentityParameters = GatherTaskIdentityParameters(bucket.Expander); TaskRequirements? requirements = _taskExecutionHost.FindTask(taskIdentityParameters); if (requirements != null) @@ -512,15 +512,15 @@ private async Task ExecuteBucket(TaskHost taskHost, ItemBucket b /// private IDictionary GatherTaskIdentityParameters(Expander expander) { - ErrorUtilities.VerifyThrowInternalNull(_taskNode, "taskNode"); // taskNode should never be null when we're calling this method. + ErrorUtilities.VerifyThrowInternalNull(_taskNode, "taskNode"); // taskNode should never be null when we're calling this method. string msbuildArchitecture = expander.ExpandIntoStringAndUnescape(_taskNode.MSBuildArchitecture ?? String.Empty, ExpanderOptions.ExpandAll, _taskNode.MSBuildArchitectureLocation ?? ElementLocation.EmptyLocation); string msbuildRuntime = expander.ExpandIntoStringAndUnescape(_taskNode.MSBuildRuntime ?? String.Empty, ExpanderOptions.ExpandAll, _taskNode.MSBuildRuntimeLocation ?? ElementLocation.EmptyLocation); IDictionary taskIdentityParameters = null; - // only bother to create a task identity parameter set if we're putting anything in there -- otherwise, - // a null set will be treated as equivalent to all parameters being "don't care". + // only bother to create a task identity parameter set if we're putting anything in there -- otherwise, + // a null set will be treated as equivalent to all parameters being "don't care". if (msbuildRuntime != String.Empty || msbuildArchitecture != String.Empty) { taskIdentityParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -819,7 +819,7 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta // Set the property "MSBuildLastTaskResult" to reflect whether the task succeeded or not. // The main use of this is if ContinueOnError is true -- so that the next task can consult the result. - // So we want it to be "false" even if ContinueOnError is true. + // So we want it to be "false" even if ContinueOnError is true. // The constants "true" and "false" should NOT be localized. They become property values. bucket.Lookup.SetProperty(ProjectPropertyInstance.Create(ReservedPropertyNames.lastTaskResult, taskResult ? "true" : "false", true/* may be reserved */, _buildRequestEntry.RequestConfiguration.Project.IsImmutable)); } @@ -886,7 +886,7 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta } else if (type == typeof(Exception) || type.GetTypeInfo().IsSubclassOf(typeof(Exception))) { - // Occasionally, when debugging a very uncommon task exception, it is useful to loop the build with + // Occasionally, when debugging a very uncommon task exception, it is useful to loop the build with // a debugger attached to break on 2nd chance exceptions. // That requires that there needs to be a way to not catch here, by setting an environment variable. if (ExceptionHandling.IsCriticalException(taskException) || (Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1")) From b830c49bd65b5e166179d4e3d9a1c0d18e6de9c3 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 20 Nov 2020 14:42:08 -0600 Subject: [PATCH 054/105] Whitespace cleanup in TaskHost --- .../Components/RequestBuilder/TaskHost.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index bd6fb1eef23..f7dcb4f1f84 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -369,7 +369,6 @@ public void Reacquire() ErrorUtilities.VerifyThrow(_yieldThreadId == Thread.CurrentThread.ManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Thread.CurrentThread.ManagedThreadId, _yieldThreadId); MSBuildEventSource.Log.ExecuteTaskYieldStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); MSBuildEventSource.Log.ExecuteTaskReacquireStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); - builderCallback.Reacquire(); MSBuildEventSource.Log.ExecuteTaskReacquireStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); _yieldThreadId = -1; @@ -408,8 +407,8 @@ public void LogErrorEvent(Microsoft.Build.Framework.BuildErrorEventArgs e) return; } - // If we are in building across process we need the events to be serializable. This method will - // check to see if we are building with multiple process and if the event is serializable. It will + // If we are in building across process we need the events to be serializable. This method will + // check to see if we are building with multiple process and if the event is serializable. It will // also log a warning if the event is not serializable and drop the logging message. if (IsRunningMultipleNodes && !IsEventSerializable(e)) { @@ -418,7 +417,7 @@ public void LogErrorEvent(Microsoft.Build.Framework.BuildErrorEventArgs e) if (_convertErrorsToWarnings) { - // Convert the error into a warning. We do this because the whole point of + // Convert the error into a warning. We do this because the whole point of // ContinueOnError is that a project author expects that the task might fail, // but wants to ignore the failures. This implies that we shouldn't be logging // errors either, because you should never have a successful build with errors. @@ -479,8 +478,8 @@ public void LogWarningEvent(Microsoft.Build.Framework.BuildWarningEventArgs e) return; } - // If we are in building across process we need the events to be serializable. This method will - // check to see if we are building with multiple process and if the event is serializable. It will + // If we are in building across process we need the events to be serializable. This method will + // check to see if we are building with multiple process and if the event is serializable. It will // also log a warning if the event is not serializable and drop the logging message. if (IsRunningMultipleNodes && !IsEventSerializable(e)) { @@ -520,8 +519,8 @@ public void LogMessageEvent(Microsoft.Build.Framework.BuildMessageEventArgs e) return; } - // If we are in building across process we need the events to be serializable. This method will - // check to see if we are building with multiple process and if the event is serializable. It will + // If we are in building across process we need the events to be serializable. This method will + // check to see if we are building with multiple process and if the event is serializable. It will // also log a warning if the event is not serializable and drop the logging message. if (IsRunningMultipleNodes && !IsEventSerializable(e)) { @@ -561,8 +560,8 @@ public void LogCustomEvent(Microsoft.Build.Framework.CustomBuildEventArgs e) return; } - // If we are in building across process we need the events to be serializable. This method will - // check to see if we are building with multiple process and if the event is serializable. It will + // If we are in building across process we need the events to be serializable. This method will + // check to see if we are building with multiple process and if the event is serializable. It will // also log a warning if the event is not serializable and drop the logging message. if (IsRunningMultipleNodes && !IsEventSerializable(e)) { @@ -786,7 +785,7 @@ public override object InitializeLifetimeService() ILease lease = (ILease)base.InitializeLifetimeService(); // Set how long a lease should be initially. Once a lease expires - // the remote object will be disconnected and it will be marked as being availiable + // the remote object will be disconnected and it will be marked as being availiable // for garbage collection int initialLeaseTime = 1; @@ -808,7 +807,7 @@ public override object InitializeLifetimeService() // increase the lease time allowing the object to stay in memory _sponsor = new ClientSponsor(); - // When a new lease is requested lets make it last 1 minutes longer. + // When a new lease is requested lets make it last 1 minutes longer. int leaseExtensionTime = 1; string leaseExtensionTimeFromEnvironment = Environment.GetEnvironmentVariable("MSBUILDENGINEPROXYLEASEEXTENSIONTIME"); @@ -845,7 +844,7 @@ internal void MarkAsInactive() ReleaseAllCores(); // Since the task has a pointer to this class it may store it in a static field. Null out - // internal data so the leak of this object doesn't lead to a major memory leak. + // internal data so the leak of this object doesn't lead to a major memory leak. _host = null; _requestEntry = null; @@ -933,7 +932,7 @@ private async Task BuildProjectFilesInParallelAsync(string[] } else { - // UNDONE: (Refactor) Investigate making this a ReadOnly collection of some sort. + // UNDONE: (Refactor) Investigate making this a ReadOnly collection of some sort. PropertyDictionary[] propertyDictionaries = new PropertyDictionary[projectFileNames.Length]; for (int i = 0; i < projectFileNames.Length; i++) From bb2d9475ddfec8530dd8e921f5ba0eb0d4765616 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 20 Nov 2020 14:49:06 -0600 Subject: [PATCH 055/105] Clarity in MockHost --- src/Shared/UnitTests/MockEngine.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index 20a719474a0..cd791ea3f2a 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -497,16 +497,15 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life { cpuCount ??= Semaphore.OpenExisting(ResourceSemaphoreName); - int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; - cpuCount.WaitOne(); + int coresAcquired = 1; // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. for (int i = 1; i < requestedCores; i++) { if (cpuCount.WaitOne(0)) { - runningTotal++; + coresAcquired++; } else { @@ -514,7 +513,9 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life } } - return runningTotal - coresAcquiredBeforeMoreCoresGetAcquired; + runningTotal += coresAcquired; + + return coresAcquired; } public void ReleaseCores(int coresToRelease) From 59af8421315be4edb3189cf08328444eec35be9f Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 20 Nov 2020 14:49:27 -0600 Subject: [PATCH 056/105] fixup! fixup! fixup! Remove BlockingWaitForCore() since it's now redundant --- src/Shared/UnitTests/MockEngine.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index cd791ea3f2a..a85f75230d1 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -530,12 +530,5 @@ public void ReleaseCores(int coresToRelease) cpuCount.Release(coresToRelease); } } - - public void BlockingWaitForCore() - { - cpuCount ??= Semaphore.OpenExisting(ResourceSemaphoreName); - - cpuCount.WaitOne(); - } } } From cacc2ac989945568b03d30f07e0880e8e984c3a9 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Mon, 23 Nov 2020 13:44:11 -0600 Subject: [PATCH 057/105] Log resource requests/releases This may be a perf hog; we should consider demoting these messages to ETW events. But for v1 they might be very valuable, so log. --- .../Components/RequestBuilder/TaskHost.cs | 9 +++++-- .../ResourceManager/ResourceManagerService.cs | 26 +++++++++++++++---- src/Build/Resources/Strings.resx | 9 +++++++ src/Build/Resources/xlf/Strings.cs.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.de.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.en.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.es.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.fr.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.it.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.ja.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.ko.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.pl.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.pt-BR.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.ru.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.tr.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.zh-Hans.xlf | 15 +++++++++++ src/Build/Resources/xlf/Strings.zh-Hant.xlf | 15 +++++++++++ 17 files changed, 247 insertions(+), 7 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index f7dcb4f1f84..cd0183d5f1c 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -689,7 +689,7 @@ public void LogTelemetry(string eventName, IDictionary propertie int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; - var coresAcquired = rms.RequestCores(requestedCores); + var coresAcquired = rms.RequestCores(requestedCores, _taskLoggingContext); if (coresAcquired.HasValue) { @@ -703,11 +703,16 @@ public void ReleaseCores(int coresToRelease) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; + if (coresToRelease > runningTotal) + { + // TODO: log + } + coresToRelease = Math.Min(runningTotal, coresToRelease); if (coresToRelease >= 1) { - rms.ReleaseCores(coresToRelease); + rms.ReleaseCores(coresToRelease, _taskLoggingContext); runningTotal -= coresToRelease; } } diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 42e9ec47b73..75079f52adc 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; using System.Threading; @@ -12,6 +13,8 @@ class ResourceManagerService : IBuildComponent { Semaphore? s = null; + ILoggingService? _loggingService; + #if DEBUG public int TotalNumberHeld = -1; public string? SemaphoreName; @@ -30,6 +33,8 @@ public void InitializeComponent(IBuildComponentHost host) int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability + _loggingService = host.LoggingService; + #if DEBUG TotalNumberHeld = 0; SemaphoreName = semaphoreName; @@ -51,12 +56,14 @@ public void ShutdownComponent() s?.Dispose(); s = null; + _loggingService = null; + #if DEBUG TotalNumberHeld = -2; #endif } - public int? RequestCores(int requestedCores) + public int? RequestCores(int requestedCores, TaskLoggingContext _taskLoggingContext) { if (s is null) { @@ -81,14 +88,18 @@ public void ShutdownComponent() { if (!s.WaitOne(0)) { - return i; + break; } } + TotalNumberHeld += i; + + _loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerRequestedCores", requestedCores, i, TotalNumberHeld); + return i; } - public void ReleaseCores(int coresToRelease) + public void ReleaseCores(int coresToRelease, TaskLoggingContext _taskLoggingContext) { if (s is null) { @@ -104,11 +115,16 @@ public void ReleaseCores(int coresToRelease) ErrorUtilities.VerifyThrow(coresToRelease > 0, "Tried to release {0} cores", coresToRelease); + if (coresToRelease > TotalNumberHeld) + { + _loggingService?.LogWarning(_taskLoggingContext.BuildEventContext, null, null, "ResourceManagerExcessRelease", coresToRelease); + } + s.Release(coresToRelease); -#if DEBUG TotalNumberHeld -= coresToRelease; -#endif + + _loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerReleasedCores", coresToRelease, TotalNumberHeld); } } } diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index dca9e8d751f..9137a36f3af 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1850,4 +1850,13 @@ Utilization: {0} Average Utilization: {1:###.0} "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Task released {0} cores and now holds {1}. + + + Task requested {0} cores and recieved {1}. It now holds {2}. + + + MSB4270: Task attempted to release {0} cores but held only {1}. + diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 0ba84491cde..1669cf8d99b 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -197,6 +197,21 @@ Počáteční hodnota vlastnosti: $({0})={1} Zdroj: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: Projekt {0} přeskočil omezení izolace grafu v odkazovaném projektu {1}. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 180fd26527c..76da1f7160f 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -197,6 +197,21 @@ Anfangswert der Eigenschaft: $({0})="{1}", Quelle: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: Das Projekt "{0}" hat Graphisolationseinschränkungen für das referenzierte Projekt "{1}" übersprungen. diff --git a/src/Build/Resources/xlf/Strings.en.xlf b/src/Build/Resources/xlf/Strings.en.xlf index 6bc335b4f99..d2cd06040db 100644 --- a/src/Build/Resources/xlf/Strings.en.xlf +++ b/src/Build/Resources/xlf/Strings.en.xlf @@ -197,6 +197,21 @@ Property initial value: $({0})="{1}" Source: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index a709c0ee4b3..c79cfa70c4e 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -197,6 +197,21 @@ Valor inicial de la propiedad: $({0})="{1}" Origen: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: El proyecto "{0}" ha omitido las restricciones de aislamiento de gráficos en el proyecto "{1}" al que se hace referencia. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 7ff10c46252..15429aff0b3 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -197,6 +197,21 @@ Valeur initiale de la propriété : $({0})="{1}" Source : {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: le projet "{0}" a ignoré les contraintes d'isolement de graphe dans le projet référencé "{1}" diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 04dd42ad112..61694cd65d5 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -197,6 +197,21 @@ Valore iniziale della proprietà: $({0})="{1}". Origine: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: il progetto "{0}" ha ignorato i vincoli di isolamento del grafico nel progetto di riferimento "{1}" diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 4094bff8c9c..7d1aeda1f44 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -197,6 +197,21 @@ プロパティの初期値: $({0})="{1}" ソース: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: プロジェクト "{0}" は、参照先のプロジェクト "{1}" で、グラフの分離制約をスキップしました diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 24d6b5e663d..d46a25590f3 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -197,6 +197,21 @@ 속성 초기 값: $({0})="{1}" 소스: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: 프로젝트 "{0}"에서 참조된 프로젝트 "{1}"의 그래프 격리 제약 조건을 건너뛰었습니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 3c70b202bfb..f1b5576cf8b 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -197,6 +197,21 @@ Wartość początkowa właściwości: $({0})=„{1}” Źródło: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: W przypadku projektu „{0}” pominięto ograniczenia izolacji grafu dla przywoływanego projektu „{1}” diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index f08a2edb3de..9039bfe421b 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -197,6 +197,21 @@ Valor inicial da propriedade: $({0})="{1}" Origem: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: o projeto "{0}" ignorou as restrições de isolamento do gráfico no projeto referenciado "{1}" diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index f86dc394210..3d92cf48316 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -197,6 +197,21 @@ Начальное значение свойства: $({0})="{1}" Источник: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: проект "{0}" пропустил ограничения изоляции графа в проекте "{1}", на который указывает ссылка. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 0b05d4f062a..39c2654fdf5 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -197,6 +197,21 @@ Özellik başlangıç değeri: $({0})="{1}" Kaynak: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: "{0}" projesi, başvurulan "{1}" projesindeki graf yalıtımı kısıtlamalarını atladı diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 8e6abc60fe5..320ce924258 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -197,6 +197,21 @@ 属性初始值: $({0})=“{1}”,源: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: 项目“{0}”已跳过所引用的项目“{1}”上的图形隔离约束 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 148f027f4d5..dddc7d712f4 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -197,6 +197,21 @@ 屬性初始值: $({0})="{1}" 來源: {2} + + MSB4270: Task attempted to release {0} cores but held only {1}. + MSB4270: Task attempted to release {0} cores but held only {1}. + + + + Task released {0} cores and now holds {1}. + Task released {0} cores and now holds {1}. + + + + Task requested {0} cores and recieved {1}. It now holds {2}. + Task requested {0} cores and recieved {1}. It now holds {2}. + + MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: 專案 "{0}" 已跳過參考專案 "{1}" 上的圖形隔離條件約束 From 3c0a8e78937d78925a01e8e12bfd581845a13d80 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 8 Dec 2020 10:28:44 -0600 Subject: [PATCH 058/105] Move clamp on release to service layer --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 7 ------- .../Components/ResourceManager/ResourceManagerService.cs | 2 ++ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index cd0183d5f1c..b9e32298b17 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -703,13 +703,6 @@ public void ReleaseCores(int coresToRelease) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; - if (coresToRelease > runningTotal) - { - // TODO: log - } - - coresToRelease = Math.Min(runningTotal, coresToRelease); - if (coresToRelease >= 1) { rms.ReleaseCores(coresToRelease, _taskLoggingContext); diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 75079f52adc..d58cb6e9ce7 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -118,6 +118,8 @@ public void ReleaseCores(int coresToRelease, TaskLoggingContext _taskLoggingCont if (coresToRelease > TotalNumberHeld) { _loggingService?.LogWarning(_taskLoggingContext.BuildEventContext, null, null, "ResourceManagerExcessRelease", coresToRelease); + + coresToRelease = TotalNumberHeld; } s.Release(coresToRelease); From ded86d5234c9b6942ab111d458829047a3c2edee Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 8 Dec 2020 10:41:08 -0600 Subject: [PATCH 059/105] Switch system off more gracefully on non-Windows --- .../ResourceManager/ResourceManagerService.cs | 44 +++++-------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index d58cb6e9ce7..ceb0ff367c3 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -15,10 +15,7 @@ class ResourceManagerService : IBuildComponent ILoggingService? _loggingService; -#if DEBUG public int TotalNumberHeld = -1; - public string? SemaphoreName; -#endif internal static IBuildComponent CreateComponent(BuildComponentType type) { @@ -29,26 +26,24 @@ internal static IBuildComponent CreateComponent(BuildComponentType type) public void InitializeComponent(IBuildComponentHost host) { - string semaphoreName = host.BuildParameters.ResourceManagerSemaphoreName; + if (NativeMethodsShared.IsWindows) + { + string semaphoreName = host.BuildParameters.ResourceManagerSemaphoreName; - int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability + int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability - _loggingService = host.LoggingService; + _loggingService = host.LoggingService; -#if DEBUG - TotalNumberHeld = 0; - SemaphoreName = semaphoreName; -#endif + TotalNumberHeld = 0; - if (NativeMethodsShared.IsWindows) - { s = new Semaphore(resourceCount, resourceCount, semaphoreName); // TODO: SemaphoreSecurity? } else { // UNDONE: just don't support gathering additional cores on non-Windows - s = new Semaphore(1, 1); + s = null; } + } public void ShutdownComponent() @@ -58,24 +53,14 @@ public void ShutdownComponent() _loggingService = null; -#if DEBUG TotalNumberHeld = -2; -#endif } public int? RequestCores(int requestedCores, TaskLoggingContext _taskLoggingContext) { if (s is null) { - if (!NativeMethodsShared.IsWindows) - { - // Since the current implementation of the cross-process resource count uses - // named semaphores, it's not usable on non-Windows, so just return the - // guaranteed resource. - return null; - } - - throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); + return null; } int i = 0; @@ -103,14 +88,9 @@ public void ReleaseCores(int coresToRelease, TaskLoggingContext _taskLoggingCont { if (s is null) { - if (!NativeMethodsShared.IsWindows) - { - // Since the current implementation of the cross-process resource count uses - // named semaphores, it's not usable on non-Windows, so just continue. - return; - } - - throw new InternalErrorException($"{nameof(ResourceManagerService)} was called while uninitialized"); + // Since the current implementation of the cross-process resource count uses + // named semaphores, it's not usable on non-Windows, so just continue. + return; } ErrorUtilities.VerifyThrow(coresToRelease > 0, "Tried to release {0} cores", coresToRelease); From 0c0c0b4361b90b617c53b795aee3fa2c7599a6b7 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 15 Dec 2020 10:09:28 -0600 Subject: [PATCH 060/105] Release nodes on reacquire --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index b9e32298b17..fcbfa21ce36 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -368,8 +368,16 @@ public void Reacquire() ErrorUtilities.VerifyThrow(_yieldThreadId != -1, "Cannot call Reacquire() before Yield()."); ErrorUtilities.VerifyThrow(_yieldThreadId == Thread.CurrentThread.ManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Thread.CurrentThread.ManagedThreadId, _yieldThreadId); MSBuildEventSource.Log.ExecuteTaskYieldStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); + MSBuildEventSource.Log.ExecuteTaskReacquireStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); + + // If a task acquires all available cores, yields, then another instance of the task comes along and blocks on RequestCores, + // the build can deadlock because the node can't be required by the first task to release the cores. Instead, treat reacquire + // as a checkpoint and release nodes here before blocking on getting the node back. + ReleaseAllCores(); + builderCallback.Reacquire(); + MSBuildEventSource.Log.ExecuteTaskReacquireStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); _yieldThreadId = -1; } From 7a4e1e05d2124fb4d4e8582bc910410427965846 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 5 Jan 2021 16:41:32 -0600 Subject: [PATCH 061/105] Implicit core for nonblocking 1 return --- .../Components/RequestBuilder/TaskHost.cs | 19 +++++++++++++++++-- .../ResourceManager/ResourceManagerService.cs | 10 +++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index fcbfa21ce36..ff4dfe95674 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -690,13 +690,12 @@ public void LogTelemetry(string eventName, IDictionary propertie #region IBuildEngine8 Members int runningTotal = 0; + bool implicitCoreUsed = false; public int? RequestCores(int requestedCores) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; - int coresAcquiredBeforeMoreCoresGetAcquired = runningTotal; - var coresAcquired = rms.RequestCores(requestedCores, _taskLoggingContext); if (coresAcquired.HasValue) @@ -704,6 +703,14 @@ public void LogTelemetry(string eventName, IDictionary propertie runningTotal += coresAcquired.Value; } + if (!implicitCoreUsed && coresAcquired == 0) + { + // If we got nothing back from the actual system, pad it with the one implicit core + // you get just for running--that way we never block and always return > 1 + implicitCoreUsed = true; + coresAcquired = 1; + } + return coresAcquired; } @@ -711,8 +718,15 @@ public void ReleaseCores(int coresToRelease) { var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; + if (implicitCoreUsed) + { + coresToRelease -= 1; + implicitCoreUsed = false; + } + if (coresToRelease >= 1) { + rms.ReleaseCores(coresToRelease, _taskLoggingContext); runningTotal -= coresToRelease; } @@ -723,6 +737,7 @@ internal void ReleaseAllCores() ReleaseCores(runningTotal); runningTotal = 0; + implicitCoreUsed = false; } #endregion diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index ceb0ff367c3..6d5f84820a5 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -16,6 +16,7 @@ class ResourceManagerService : IBuildComponent ILoggingService? _loggingService; public int TotalNumberHeld = -1; + public int Count = 0; internal static IBuildComponent CreateComponent(BuildComponentType type) { @@ -32,6 +33,8 @@ public void InitializeComponent(IBuildComponentHost host) int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability + Count = resourceCount; + _loggingService = host.LoggingService; TotalNumberHeld = 0; @@ -63,13 +66,10 @@ public void ShutdownComponent() return null; } - int i = 0; - - // First core gets a blocking wait: the user task wants to do *something* - s.WaitOne(); + int i; // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. - for (i = 1; i < requestedCores; i++) + for (i = 0; i < requestedCores; i++) { if (!s.WaitOne(0)) { From 3d2575c37819c51eb0f460787c265fa406123164 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 5 Jan 2021 16:48:13 -0600 Subject: [PATCH 062/105] Allow environment variable MSBUILDRESOURCEMANAGEROVERSUBSCRIPTION to control oversubscription --- .../Components/ResourceManager/ResourceManagerService.cs | 4 +++- src/Shared/Traits.cs | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 6d5f84820a5..68f21234456 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -3,6 +3,8 @@ using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; +using Microsoft.Build.Utilities; + using System.Threading; #nullable enable @@ -31,7 +33,7 @@ public void InitializeComponent(IBuildComponentHost host) { string semaphoreName = host.BuildParameters.ResourceManagerSemaphoreName; - int resourceCount = host.BuildParameters.MaxNodeCount; // TODO: tweakability + int resourceCount = host.BuildParameters.MaxNodeCount + Traits.Instance.ResourceManagerOversubscription; Count = resourceCount; diff --git a/src/Shared/Traits.cs b/src/Shared/Traits.cs index 3522c0972c1..6a4e3add7f3 100644 --- a/src/Shared/Traits.cs +++ b/src/Shared/Traits.cs @@ -96,6 +96,12 @@ public Traits() /// public readonly int LogPropertyTracking = ParseIntFromEnvironmentVariableOrDefault("MsBuildLogPropertyTracking", 0); // Default to logging nothing via the property tracker. + /// + /// Allow tasks to collect more resources than the default. + /// + public readonly int ResourceManagerOversubscription = ParseIntFromEnvironmentVariableOrDefault("MSBUILDRESOURCEMANAGEROVERSUBSCRIPTION", 0); // Default to maxcpucount + + private static int ParseIntFromEnvironmentVariableOrDefault(string environmentVariable, int defaultValue) { return int.TryParse(Environment.GetEnvironmentVariable(environmentVariable), out int result) From c416bf55b2d6bf111a9e94522357a0f02fdb39f7 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Tue, 5 Jan 2021 17:05:46 -0600 Subject: [PATCH 063/105] Revert "Release nodes on reacquire" This reverts commit 0c0c0b4361b90b617c53b795aee3fa2c7599a6b7. --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index ff4dfe95674..40316702cce 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -368,16 +368,8 @@ public void Reacquire() ErrorUtilities.VerifyThrow(_yieldThreadId != -1, "Cannot call Reacquire() before Yield()."); ErrorUtilities.VerifyThrow(_yieldThreadId == Thread.CurrentThread.ManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Thread.CurrentThread.ManagedThreadId, _yieldThreadId); MSBuildEventSource.Log.ExecuteTaskYieldStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); - MSBuildEventSource.Log.ExecuteTaskReacquireStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); - - // If a task acquires all available cores, yields, then another instance of the task comes along and blocks on RequestCores, - // the build can deadlock because the node can't be required by the first task to release the cores. Instead, treat reacquire - // as a checkpoint and release nodes here before blocking on getting the node back. - ReleaseAllCores(); - builderCallback.Reacquire(); - MSBuildEventSource.Log.ExecuteTaskReacquireStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); _yieldThreadId = -1; } From 444ffde90b2953cd3597eec0c77303dd5c017610 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 15 Jan 2021 15:53:26 -0600 Subject: [PATCH 064/105] WIP: acquire/release resources in the scheduler --- .../ResourceManager/ResourceManagerService.cs | 15 +++++++++++++++ .../BackEnd/Components/Scheduler/Scheduler.cs | 19 ++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 68f21234456..087f4018bf0 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -110,5 +110,20 @@ public void ReleaseCores(int coresToRelease, TaskLoggingContext _taskLoggingCont _loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerReleasedCores", coresToRelease, TotalNumberHeld); } + + internal void RequireCores(int requestedCores) + { + if (s is null) + { + // Since the current implementation of the cross-process resource count uses + // named semaphores, it's not usable on non-Windows, so just continue. + return; + } + + if (!s.WaitOne()) + { + ErrorUtilities.ThrowInternalError("Couldn't get a core to run a task even with infinite timeout"); + } + } } } diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 0ce340f4828..fc7b75da51f 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -9,6 +9,8 @@ using System.Linq; using System.Text; using System.Threading; + +using Microsoft.Build.BackEnd.Components.ResourceManager; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -70,6 +72,8 @@ internal class Scheduler : IScheduler ///
private int _nodeLimitOffset; + private int _resourceManagedCoresUsed = 0; + /// /// { nodeId -> NodeInfo } /// A list of nodes we know about. For the non-distributed case, there will be no more nodes than the @@ -105,6 +109,7 @@ internal class Scheduler : IScheduler /// The configuration cache. /// private IConfigCache _configCache; + private ResourceManagerService _resourceManager; /// /// The results cache. @@ -529,6 +534,7 @@ public void InitializeComponent(IBuildComponentHost host) _componentHost = host; _resultsCache = (IResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache); _configCache = (IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache); + _resourceManager = (ResourceManagerService)_componentHost.GetComponent(BuildComponentType.TaskResourceManager); } /// @@ -928,6 +934,7 @@ private void AssignUnscheduledRequestsWithConfigurationCountLevelling(List 1) { - 1 => 1, - 2 => _componentHost.BuildParameters.MaxNodeCount + 1 + _nodeLimitOffset, - _ => _componentHost.BuildParameters.MaxNodeCount + 2 + _nodeLimitOffset, - }; + // Delegate the oversubscription factor to the resource manager + // but continue to support a manual override here + limit = _resourceManager.Count + _nodeLimitOffset; + } // We're at our limit of schedulable requests if: // (1) MaxNodeCount requests are currently executing From 761fb6a0722d76e8956e5ec006f49d2c2bb1628e Mon Sep 17 00:00:00 2001 From: Mihai Codoban Date: Fri, 15 Jan 2021 14:17:14 -0800 Subject: [PATCH 065/105] Update documentation/specs/resource-management.md Co-authored-by: Rainer Sigwald --- documentation/specs/resource-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/resource-management.md b/documentation/specs/resource-management.md index 626efb30d22..7b84e50efc9 100644 --- a/documentation/specs/resource-management.md +++ b/documentation/specs/resource-management.md @@ -1,6 +1,6 @@ # Managing tools with their own parallelism in MSBuild -MSBuild supports building projects in parallel using multiple processes. Most users opt into `NUM_PROCS` parallelism at the MSBuild layer. +MSBuild supports building projects in parallel using multiple processes. Most users opt into `Environment.ProcessorCount` parallelism at the MSBuild layer. In addition, tools sometimes support parallel execution. The Visual C++ compiler `cl.exe` supports `/MP[n]`, which parallelizes compilation at the translation-unit (file) level. If a number isn't specified, it defaults to `NUM_PROCS`. From 2ce2af86da1fa2463dcc98c2e5aaa2a084845b9b Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 19 Feb 2021 13:46:23 +0100 Subject: [PATCH 066/105] Remove Semaphore-based logic --- .../BackEnd/BuildManager/BuildParameters.cs | 8 -- .../ResourceManager/ResourceManagerService.cs | 86 ++----------------- .../BackEnd/Components/Scheduler/Scheduler.cs | 15 ++-- src/Shared/UnitTests/MockEngine.cs | 33 +------ 4 files changed, 13 insertions(+), 129 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index 4869dfbda9d..1259648e255 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -215,7 +215,6 @@ public class BuildParameters : ITranslatable private string[] _inputResultsCacheFiles; private string _outputResultsCacheFile; - private string _resourceManagerSemaphoreName = $"MSBuild.{Guid.NewGuid().ToString()}"; /// /// Constructor for those who intend to set all properties themselves. @@ -774,12 +773,6 @@ public string OutputResultsCacheFile set => _outputResultsCacheFile = value; } - public string ResourceManagerSemaphoreName - { - get => _resourceManagerSemaphoreName; - set => _resourceManagerSemaphoreName = value; - } - /// /// Determines whether MSBuild will save the results of builds after EndBuild to speed up future builds. /// @@ -856,7 +849,6 @@ void ITranslatable.Translate(ITranslator translator) translator.TranslateEnum(ref _projectLoadSettings, (int) _projectLoadSettings); translator.Translate(ref _interactive); translator.Translate(ref _isolateProjects); - translator.Translate(ref _resourceManagerSemaphoreName); // ProjectRootElementCache is not transmitted. // ResetCaches is not transmitted. diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index 087f4018bf0..cee32b41ff9 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -13,9 +13,7 @@ namespace Microsoft.Build.BackEnd.Components.ResourceManager { class ResourceManagerService : IBuildComponent { - Semaphore? s = null; - - ILoggingService? _loggingService; + //ILoggingService? _loggingService; public int TotalNumberHeld = -1; public int Count = 0; @@ -29,101 +27,29 @@ internal static IBuildComponent CreateComponent(BuildComponentType type) public void InitializeComponent(IBuildComponentHost host) { - if (NativeMethodsShared.IsWindows) - { - string semaphoreName = host.BuildParameters.ResourceManagerSemaphoreName; - - int resourceCount = host.BuildParameters.MaxNodeCount + Traits.Instance.ResourceManagerOversubscription; - - Count = resourceCount; - - _loggingService = host.LoggingService; - - TotalNumberHeld = 0; - - s = new Semaphore(resourceCount, resourceCount, semaphoreName); // TODO: SemaphoreSecurity? - } - else - { - // UNDONE: just don't support gathering additional cores on non-Windows - s = null; - } } public void ShutdownComponent() { - s?.Dispose(); - s = null; - - _loggingService = null; + //_loggingService = null; TotalNumberHeld = -2; } public int? RequestCores(int requestedCores, TaskLoggingContext _taskLoggingContext) { - if (s is null) - { - return null; - } - - int i; - - // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. - for (i = 0; i < requestedCores; i++) - { - if (!s.WaitOne(0)) - { - break; - } - } - - TotalNumberHeld += i; + return null; - _loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerRequestedCores", requestedCores, i, TotalNumberHeld); - - return i; + // _loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerRequestedCores", requestedCores, i, TotalNumberHeld); } public void ReleaseCores(int coresToRelease, TaskLoggingContext _taskLoggingContext) { - if (s is null) - { - // Since the current implementation of the cross-process resource count uses - // named semaphores, it's not usable on non-Windows, so just continue. - return; - } - ErrorUtilities.VerifyThrow(coresToRelease > 0, "Tried to release {0} cores", coresToRelease); + return; - if (coresToRelease > TotalNumberHeld) - { - _loggingService?.LogWarning(_taskLoggingContext.BuildEventContext, null, null, "ResourceManagerExcessRelease", coresToRelease); - - coresToRelease = TotalNumberHeld; - } - - s.Release(coresToRelease); - - TotalNumberHeld -= coresToRelease; - - _loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerReleasedCores", coresToRelease, TotalNumberHeld); - } - - internal void RequireCores(int requestedCores) - { - if (s is null) - { - // Since the current implementation of the cross-process resource count uses - // named semaphores, it's not usable on non-Windows, so just continue. - return; - } - - if (!s.WaitOne()) - { - ErrorUtilities.ThrowInternalError("Couldn't get a core to run a task even with infinite timeout"); - } + //_loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerReleasedCores", coresToRelease, TotalNumberHeld); } } } diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index fc7b75da51f..3777ce994af 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -72,7 +72,7 @@ internal class Scheduler : IScheduler /// private int _nodeLimitOffset; - private int _resourceManagedCoresUsed = 0; + // private int _resourceManagedCoresUsed = 0; /// /// { nodeId -> NodeInfo } @@ -934,7 +934,6 @@ private void AssignUnscheduledRequestsWithConfigurationCountLevelling(List 1) + int limit = _componentHost.BuildParameters.MaxNodeCount switch { - // Delegate the oversubscription factor to the resource manager - // but continue to support a manual override here - limit = _resourceManager.Count + _nodeLimitOffset; - } + 1 => 1, + 2 => _componentHost.BuildParameters.MaxNodeCount + 1 + _nodeLimitOffset, + _ => _componentHost.BuildParameters.MaxNodeCount + 2 + _nodeLimitOffset, + }; // We're at our limit of schedulable requests if: // (1) MaxNodeCount requests are currently executing diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index a85f75230d1..dd8d06ca6ec 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -491,44 +491,13 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life return obj; } - int runningTotal = 0; - Semaphore cpuCount; public int? RequestCores(int requestedCores) { - cpuCount ??= Semaphore.OpenExisting(ResourceSemaphoreName); - - cpuCount.WaitOne(); - int coresAcquired = 1; - - // Keep requesting cores until we can't anymore, or we've gotten the number of cores we wanted. - for (int i = 1; i < requestedCores; i++) - { - if (cpuCount.WaitOne(0)) - { - coresAcquired++; - } - else - { - break; - } - } - - runningTotal += coresAcquired; - - return coresAcquired; + return null; } public void ReleaseCores(int coresToRelease) { - cpuCount ??= Semaphore.OpenExisting(ResourceSemaphoreName); - - coresToRelease = Math.Min(runningTotal, coresToRelease); - - // if we attempt to release 0 cores, Release throws an exception. - if(coresToRelease > 0) - { - cpuCount.Release(coresToRelease); - } } } } From e3439e40b4b22a800331d40509c9d28921eaf0a4 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Mon, 22 Feb 2021 10:06:22 +0100 Subject: [PATCH 067/105] Plumbing to pass ResourceRequest to scheduler and ResourceResponse back to task --- .../BackEnd/BuildRequestEngine_Tests.cs | 13 ++++ .../BackEnd/TargetBuilder_Tests.cs | 15 ++++ .../BackEnd/TargetEntry_Tests.cs | 15 ++++ .../BackEnd/TaskBuilder_Tests.cs | 15 ++++ src/Build.UnitTests/BackEnd/TaskHost_Tests.cs | 28 ++++++++ .../BackEnd/BuildManager/BuildManager.cs | 22 ++++++ .../BuildRequestEngine/BuildRequestEngine.cs | 39 +++++++++++ .../BuildRequestEngine/IBuildRequestEngine.cs | 17 +++++ .../RequestBuilder/IRequestBuilder.cs | 10 +++ .../RequestBuilder/IRequestBuilderCallback.cs | 13 ++++ .../RequestBuilder/RequestBuilder.cs | 70 +++++++++++++++++++ .../RequestBuilder/TargetBuilder.cs | 16 +++++ .../Components/RequestBuilder/TaskHost.cs | 53 +++++++------- .../Components/Scheduler/IScheduler.cs | 10 +++ .../BackEnd/Components/Scheduler/Scheduler.cs | 17 +++++ src/Build/BackEnd/Node/InProcNode.cs | 28 ++++++++ src/Build/BackEnd/Node/OutOfProcNode.cs | 26 +++++++ src/Build/Microsoft.Build.csproj | 2 + src/Shared/INodePacket.cs | 12 +++- 19 files changed, 395 insertions(+), 26 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs index 185e828aae6..a509e6489f7 100644 --- a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs @@ -74,6 +74,8 @@ public MockRequestBuilder() public event BuildRequestBlockedDelegate OnBuildRequestBlocked; + public event ResourceRequestDelegate OnResourceRequest; + public void BuildRequest(NodeLoggingContext context, BuildRequestEntry entry) { Assert.Null(_builderThread); // "Received BuildRequest while one was in progress" @@ -171,6 +173,11 @@ public void RaiseRequestBlocked(BuildRequestEntry entry, int blockingId, string OnBuildRequestBlocked?.Invoke(entry, blockingId, blockingTarget, null); } + public void RaiseResourceRequest(ResourceRequest request) + { + OnResourceRequest?.Invoke(request); + } + public void ContinueRequest() { if (ThrowExceptionOnContinue) @@ -180,6 +187,11 @@ public void ContinueRequest() _continueEvent.Set(); } + public void ContinueRequestWithResources(ResourceResponse response) + { + // TODO + } + public void CancelRequest() { this.BeginCancel(); @@ -305,6 +317,7 @@ private void ConfigureEngine(IBuildRequestEngine engine) engine.OnRequestResumed += this.Engine_RequestResumed; engine.OnStatusChanged += this.Engine_EngineStatusChanged; engine.OnEngineException += this.Engine_Exception; + engine.OnResourceRequest += e => { }; // TODO } /// diff --git a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs index 9d8db26b192..6f62bba78eb 100644 --- a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs @@ -1417,6 +1417,21 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() { } + /// + /// Empty impl + /// + int? IRequestBuilderCallback.RequestCores(int requestedCores) + { + return null; + } + + /// + /// Empty impl + /// + void IRequestBuilderCallback.ReleaseCores(int coresToRelease) + { + } + #endregion /// diff --git a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs index 54c1888e2dd..4688c52349f 100644 --- a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs @@ -978,6 +978,21 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() { } + /// + /// Empty impl + /// + int? IRequestBuilderCallback.RequestCores(int requestedCores) + { + return null; + } + + /// + /// Empty impl + /// + void IRequestBuilderCallback.ReleaseCores(int coresToRelease) + { + } + #endregion /// diff --git a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs index ed6ff1db561..b5200cf78a5 100644 --- a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs @@ -755,6 +755,21 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() { } + /// + /// Empty impl + /// + int? IRequestBuilderCallback.RequestCores(int requestedCores) + { + return null; + } + + /// + /// Empty impl + /// + void IRequestBuilderCallback.ReleaseCores(int coresToRelease) + { + } + #endregion #region IRequestBuilderCallback Members diff --git a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs index 3996c7221e8..5fd7ac4624c 100644 --- a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs @@ -1247,6 +1247,11 @@ internal MockIRequestBuilderCallback(BuildResult[] buildResultsToReturn) /// Not Implemented /// public event BuildRequestBlockedDelegate OnBuildRequestBlocked; + + /// + /// Not Implemented + /// + public event ResourceRequestDelegate OnResourceRequest; #pragma warning restore /// @@ -1294,6 +1299,21 @@ public void ExitMSBuildCallbackState() { } + /// + /// Mock + /// + public int? RequestCores(int requestedCores) + { + return null; + } + + /// + /// Mock + /// + public void ReleaseCores(int coresToRelease) + { + } + /// /// Mock of the Block on target in progress. /// @@ -1318,6 +1338,14 @@ public void ContinueRequest() throw new NotImplementedException(); } + /// + /// Not Implemented + /// + public void ContinueRequestWithResources(ResourceResponse response) + { + throw new NotImplementedException(); + } + /// /// Not Implemented /// diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index e071160c912..af19a40875c 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -462,6 +462,7 @@ public void BeginBuild(BuildParameters parameters) _nodeManager.RegisterPacketHandler(NodePacketType.BuildResult, BuildResult.FactoryForDeserialization, this); _nodeManager.RegisterPacketHandler(NodePacketType.NodeShutdown, NodeShutdown.FactoryForDeserialization, this); _nodeManager.RegisterPacketHandler(NodePacketType.ResolveSdkRequest, SdkResolverRequest.FactoryForDeserialization, SdkResolverService as INodePacketHandler); + _nodeManager.RegisterPacketHandler(NodePacketType.ResourceRequest, ResourceRequest.FactoryForDeserialization, this); if (_threadException != null) { @@ -1537,6 +1538,11 @@ private void ProcessPacket(int node, INodePacket packet) HandleResult(node, result); break; + case NodePacketType.ResourceRequest: + ResourceRequest request = ExpectPacketType(packet, NodePacketType.ResourceRequest); + HandleResourceRequest(node, request); + break; + case NodePacketType.NodeShutdown: // Remove the node from the list of active nodes. When they are all done, we have shut down fully NodeShutdown shutdownPacket = ExpectPacketType(packet, NodePacketType.NodeShutdown); @@ -2168,6 +2174,22 @@ private void HandleNewRequest(int node, BuildRequestBlocker blocker) PerformSchedulingActions(response); } + private void HandleResourceRequest(int node, ResourceRequest request) + { + if (request.IsAcquire) + { + var coresAcquired = _scheduler.RequestCores(request.NumCores); + var response = new ResourceResponse(request.BlockedRequestId, coresAcquired ?? -1); + + _nodeManager.SendData(node, response); + } + else + { + _scheduler.ReleaseCores(request.NumCores); + // No response needed. + } + } + /// /// Handles a configuration request coming from a node. /// diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index 05e6671a6d6..859010d5d1d 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -164,6 +164,11 @@ internal BuildRequestEngine() /// public event EngineExceptionDelegate OnEngineException; + /// + /// Raised when resources are requested. + /// + public event ResourceRequestDelegate OnResourceRequest; + /// /// Returns the current engine status. /// @@ -454,6 +459,21 @@ public void UnblockBuildRequest(BuildRequestUnblocker unblocker) isLastTask: false); } + /// + /// Notifies the engine of a resource response granting the node resources. + /// + /// The resource response. + public void GrantResources(ResourceResponse response) + { + QueueAction( + () => + { + BuildRequestEntry entry = _requestsByGlobalRequestId[response.BlockedRequestId]; + entry.Builder.ContinueRequestWithResources(response); + }, + isLastTask: false); + } + /// /// Reports a configuration response to the request, allowing it to satisfy outstanding requests. /// @@ -667,6 +687,15 @@ private void RaiseNewConfigurationRequest(BuildRequestConfiguration config) OnNewConfigurationRequest?.Invoke(config); } + /// + /// Raises OnResourceRequest event. + /// + /// The resource request. + private void RaiseResourceRequest(ResourceRequest request) + { + OnResourceRequest?.Invoke(request); + } + #endregion /// @@ -773,6 +802,7 @@ private void EvaluateRequestStates() // Shut it down because we already have enough in reserve. completedEntry.Builder.OnNewBuildRequests -= Builder_OnNewBuildRequests; completedEntry.Builder.OnBuildRequestBlocked -= Builder_OnBlockedRequest; + completedEntry.Builder.OnResourceRequest -= Builder_OnResourceRequest; ((IBuildComponent)completedEntry.Builder).ShutdownComponent(); BuildRequestConfiguration configuration = _configCache[completedEntry.Request.ConfigurationId]; @@ -914,6 +944,7 @@ private IRequestBuilder GetRequestBuilder() // state changes. builder.OnNewBuildRequests += Builder_OnNewBuildRequests; builder.OnBuildRequestBlocked += Builder_OnBlockedRequest; + builder.OnResourceRequest += Builder_OnResourceRequest; return builder; } @@ -979,6 +1010,14 @@ private void Builder_OnBlockedRequest(BuildRequestEntry issuingEntry, int blocki isLastTask: false); } + /// + /// Called when the request builder needs to request resources. + /// + private void Builder_OnResourceRequest(ResourceRequest request) + { + RaiseResourceRequest(request); + } + #endregion /// diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/IBuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/IBuildRequestEngine.cs index 75cc6ef4dc9..13d40bd7c19 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/IBuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/IBuildRequestEngine.cs @@ -39,6 +39,12 @@ namespace Microsoft.Build.BackEnd /// The configuration needing an ID internal delegate void NewConfigurationRequestDelegate(BuildRequestConfiguration config); + /// + /// Callback for event raised when a resource is requested. + /// + /// The resources being requested + internal delegate void ResourceRequestDelegate(ResourceRequest request); + /// /// Callback for event raised when there is an unhandled exception in the engine. /// @@ -110,6 +116,11 @@ internal interface IBuildRequestEngine /// event NewConfigurationRequestDelegate OnNewConfigurationRequest; + /// + /// Raised when resources are requested. + /// + event ResourceRequestDelegate OnResourceRequest; + /// /// Raised when an unhandled exception occurs in the engine. /// @@ -154,6 +165,12 @@ internal interface IBuildRequestEngine /// The unblocking information void UnblockBuildRequest(BuildRequestUnblocker unblocker); + /// + /// Notifies the engine of a resource response granting the node resources. + /// + /// The resource response. + void GrantResources(ResourceResponse response); + /// /// Notifies the engine of a configuration response packet, typically generated by the Build Request Manager. This packet is used to set /// the global configuration ID for a specific configuration. diff --git a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilder.cs index a0de5afca83..5db9a001e46 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilder.cs @@ -48,6 +48,11 @@ internal interface IRequestBuilder /// event BuildRequestBlockedDelegate OnBuildRequestBlocked; + /// + /// Raised when resources are requested. + /// + event ResourceRequestDelegate OnResourceRequest; + /// /// Builds the request contained in the specified entry. /// @@ -60,6 +65,11 @@ internal interface IRequestBuilder /// void ContinueRequest(); + /// + /// Continues building a request which was previously waiting for a resource grant. + /// + void ContinueRequestWithResources(ResourceResponse response); + /// /// Cancels an existing request. /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs index 63b77897dd5..bdf7a6af7d8 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs @@ -55,5 +55,18 @@ internal interface IRequestBuilderCallback /// Exits the previous MSBuild callback state. /// void ExitMSBuildCallbackState(); + + /// + /// Requests CPU resources from the scheduler. + /// + /// Number of logical cores being requested. + /// Number of logical cores actually granted. + int? RequestCores(int requestedCores); + + /// + /// Returns CPU resources to the scheduler. + /// + /// Number of logical cores being returned. + void ReleaseCores(int coresToRelease); } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index d5b83a1566c..0fcef27b0ff 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -45,11 +45,21 @@ internal class RequestBuilder : IRequestBuilder, IRequestBuilderCallback, IBuild /// private AutoResetEvent _continueEvent; + /// + /// The event to signal that this request should wake up from its wait state after granting resources. + /// + private AutoResetEvent _continueWithResourcesEvent; + /// /// The results used when a build request entry continues. /// private IDictionary _continueResults; + /// + /// The resources granted when a build request entry continues. + /// + private ResourceResponse _continueResources; + /// /// The task representing the currently-executing build request. /// @@ -107,6 +117,7 @@ internal RequestBuilder() { _terminateEvent = new ManualResetEvent(false); _continueEvent = new AutoResetEvent(false); + _continueWithResourcesEvent = new AutoResetEvent(false); } /// @@ -124,6 +135,11 @@ internal RequestBuilder() /// public event BuildRequestBlockedDelegate OnBuildRequestBlocked; + /// + /// The event raised when resources are requested. + /// + public event ResourceRequestDelegate OnResourceRequest; + /// /// The current block type /// @@ -220,6 +236,20 @@ public void ContinueRequest() _continueEvent.Set(); } + /// + /// Continues a build request after receiving a resource response. + /// + public void ContinueRequestWithResources(ResourceResponse response) + { + ErrorUtilities.VerifyThrow(HasActiveBuildRequest, "Request not building"); + ErrorUtilities.VerifyThrow(!_terminateEvent.WaitOne(0), "Request already terminated"); + ErrorUtilities.VerifyThrow(!_continueWithResourcesEvent.WaitOne(0), "Request already continued"); + VerifyEntryInActiveState(); + + _continueResources = response; + _continueWithResourcesEvent.Set(); + } + /// /// Terminates the build request /// @@ -460,6 +490,37 @@ public void ExitMSBuildCallbackState() _inMSBuildCallback = false; } + /// + /// Requests CPU resources from the scheduler. + /// + public int? RequestCores(int requestedCores) + { + VerifyIsNotZombie(); + RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, requestedCores)); + + WaitHandle[] handles = new WaitHandle[] { _terminateEvent, _continueWithResourcesEvent }; + + int handle = WaitHandle.WaitAny(handles); + + if (handle == 0) + { + // We've been aborted + throw new BuildAbortedException(); + } + + VerifyEntryInActiveState(); + return _continueResources.NumCores; + } + + /// + /// Returns CPU resources to the scheduler. + /// + public void ReleaseCores(int coresToRelease) + { + VerifyIsNotZombie(); + RaiseResourceRequest(new ResourceRequest(coresToRelease)); + } + #endregion #region IBuildComponent Members @@ -986,6 +1047,15 @@ private void RaiseOnBlockedRequest(int blockingGlobalRequestId, string blockingT OnBuildRequestBlocked?.Invoke(_requestEntry, blockingGlobalRequestId, blockingTarget, partialBuildResult); } + /// + /// Invokes the OnResourceRequest event + /// + /// + private void RaiseResourceRequest(ResourceRequest request) + { + OnResourceRequest?.Invoke(request); + } + /// /// This method is called to reset the current directory to the one appropriate for this project. It should be called any time /// the project is resumed. diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs index d79147775b7..13f6ce418ff 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs @@ -363,6 +363,22 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() _requestBuilderCallback.ExitMSBuildCallbackState(); } + /// + /// Requests CPU resources from the scheduler. + /// + int? IRequestBuilderCallback.RequestCores(int requestedCores) + { + return _requestBuilderCallback.RequestCores(requestedCores); + } + + /// + /// Returns CPU resources to the scheduler. + /// + void IRequestBuilderCallback.ReleaseCores(int coresToRelease) + { + _requestBuilderCallback.ReleaseCores(coresToRelease); + } + #endregion /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 40316702cce..c961eb9db8b 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -686,41 +686,44 @@ public void LogTelemetry(string eventName, IDictionary propertie public int? RequestCores(int requestedCores) { - var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; + lock (_callbackMonitor) + { + IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; + var coresAcquired = builderCallback.RequestCores(requestedCores); - var coresAcquired = rms.RequestCores(requestedCores, _taskLoggingContext); + if (coresAcquired.HasValue) + { + runningTotal += coresAcquired.Value; + } - if (coresAcquired.HasValue) - { - runningTotal += coresAcquired.Value; - } + if (!implicitCoreUsed && coresAcquired == 0) + { + // If we got nothing back from the actual system, pad it with the one implicit core + // you get just for running--that way we never block and always return > 1 + implicitCoreUsed = true; + coresAcquired = 1; + } - if (!implicitCoreUsed && coresAcquired == 0) - { - // If we got nothing back from the actual system, pad it with the one implicit core - // you get just for running--that way we never block and always return > 1 - implicitCoreUsed = true; - coresAcquired = 1; + return coresAcquired; } - - return coresAcquired; } public void ReleaseCores(int coresToRelease) { - var rms = _host.GetComponent(BuildComponentType.TaskResourceManager) as ResourceManagerService; - - if (implicitCoreUsed) - { - coresToRelease -= 1; - implicitCoreUsed = false; - } - - if (coresToRelease >= 1) + lock (_callbackMonitor) { + if (implicitCoreUsed) + { + coresToRelease -= 1; + implicitCoreUsed = false; + } - rms.ReleaseCores(coresToRelease, _taskLoggingContext); - runningTotal -= coresToRelease; + if (coresToRelease >= 1) + { + IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; + builderCallback.ReleaseCores(coresToRelease); + runningTotal -= coresToRelease; + } } } diff --git a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs index eb30122e633..cc84d585988 100644 --- a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs @@ -68,5 +68,15 @@ internal interface IScheduler : IBuildComponent /// Writes a detailed summary of the build state which includes informaiton about the scheduling plan. /// void WriteDetailedSummary(int submissionId); + + /// + /// Requests CPU resources. + /// + int? RequestCores(int requestCores); + + /// + /// Returns CPU resources. + /// + void ReleaseCores(int coresToRelease); } } diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 3777ce994af..b25c0595188 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -521,6 +521,23 @@ public void WriteDetailedSummary(int submissionId) WriteNodeUtilizationGraph(loggingService, context, false /* useConfigurations */); } + /// + /// Requests CPU resources. + /// + public int? RequestCores(int requestCores) + { + // TODO: ladipro + return null; + } + + /// + /// Returns CPU resources. + /// + public void ReleaseCores(int coresToRelease) + { + // TODO: ladipro + } + #endregion #region IBuildComponent Members diff --git a/src/Build/BackEnd/Node/InProcNode.cs b/src/Build/BackEnd/Node/InProcNode.cs index f0940fa3fb7..bfcecffd271 100644 --- a/src/Build/BackEnd/Node/InProcNode.cs +++ b/src/Build/BackEnd/Node/InProcNode.cs @@ -96,6 +96,11 @@ internal class InProcNode : INode, INodePacketFactory /// private readonly RequestCompleteDelegate _requestCompleteEventHandler; + /// + /// Handler for resource request events. + /// + private readonly ResourceRequestDelegate _resourceRequestHandler; + /// /// Constructor. /// @@ -113,6 +118,7 @@ public InProcNode(IBuildComponentHost componentHost, INodeEndpoint inProcNodeEnd _newConfigurationRequestEventHandler = OnNewConfigurationRequest; _requestBlockedEventHandler = OnNewRequest; _requestCompleteEventHandler = OnRequestComplete; + _resourceRequestHandler = OnResourceRequest; } #region INode Members @@ -260,6 +266,17 @@ private void OnNewConfigurationRequest(BuildRequestConfiguration config) } } + /// + /// Event handler for the BuildEngine's OnResourceRequest event. + /// + private void OnResourceRequest(ResourceRequest request) + { + if (_nodeEndpoint.LinkStatus == LinkStatus.Active) + { + _nodeEndpoint.SendData(request); + } + } + /// /// Event handler for the LoggingService's OnLoggingThreadException event. /// @@ -354,6 +371,7 @@ private NodeEngineShutdownReason HandleShutdown(out Exception exception) _buildRequestEngine.OnNewConfigurationRequest -= _newConfigurationRequestEventHandler; _buildRequestEngine.OnRequestBlocked -= _requestBlockedEventHandler; _buildRequestEngine.OnRequestComplete -= _requestCompleteEventHandler; + _buildRequestEngine.OnResourceRequest -= _resourceRequestHandler; return _shutdownReason; } @@ -388,6 +406,10 @@ private void HandlePacket(INodePacket packet) case NodePacketType.NodeBuildComplete: HandleNodeBuildComplete(packet as NodeBuildComplete); break; + + case NodePacketType.ResourceResponse: + HandleResourceResponse(packet as ResourceResponse); + break; } } @@ -482,6 +504,7 @@ private void HandleNodeConfiguration(NodeConfiguration configuration) _buildRequestEngine.OnNewConfigurationRequest += _newConfigurationRequestEventHandler; _buildRequestEngine.OnRequestBlocked += _requestBlockedEventHandler; _buildRequestEngine.OnRequestComplete += _requestCompleteEventHandler; + _buildRequestEngine.OnResourceRequest += _resourceRequestHandler; if (_shutdownException != null) { @@ -500,5 +523,10 @@ private void HandleNodeBuildComplete(NodeBuildComplete buildComplete) _shutdownReason = buildComplete.PrepareForReuse ? NodeEngineShutdownReason.BuildCompleteReuse : NodeEngineShutdownReason.BuildComplete; _shutdownEvent.Set(); } + + private void HandleResourceResponse(ResourceResponse response) + { + _buildRequestEngine.GrantResources(response); + } } } diff --git a/src/Build/BackEnd/Node/OutOfProcNode.cs b/src/Build/BackEnd/Node/OutOfProcNode.cs index 0ae529953d0..d70930c1493 100644 --- a/src/Build/BackEnd/Node/OutOfProcNode.cs +++ b/src/Build/BackEnd/Node/OutOfProcNode.cs @@ -171,6 +171,7 @@ public OutOfProcNode() _buildRequestEngine.OnNewConfigurationRequest += OnNewConfigurationRequest; _buildRequestEngine.OnRequestBlocked += OnNewRequest; _buildRequestEngine.OnRequestComplete += OnRequestComplete; + _buildRequestEngine.OnResourceRequest += OnResourceRequest; (this as INodePacketFactory).RegisterPacketHandler(NodePacketType.BuildRequest, BuildRequest.FactoryForDeserialization, this); (this as INodePacketFactory).RegisterPacketHandler(NodePacketType.BuildRequestConfiguration, BuildRequestConfiguration.FactoryForDeserialization, this); @@ -178,6 +179,7 @@ public OutOfProcNode() (this as INodePacketFactory).RegisterPacketHandler(NodePacketType.BuildRequestUnblocker, BuildRequestUnblocker.FactoryForDeserialization, this); (this as INodePacketFactory).RegisterPacketHandler(NodePacketType.NodeConfiguration, NodeConfiguration.FactoryForDeserialization, this); (this as INodePacketFactory).RegisterPacketHandler(NodePacketType.NodeBuildComplete, NodeBuildComplete.FactoryForDeserialization, this); + (this as INodePacketFactory).RegisterPacketHandler(NodePacketType.ResourceResponse, ResourceResponse.FactoryForDeserialization, this); (this as INodePacketFactory).RegisterPacketHandler(NodePacketType.ResolveSdkResponse, SdkResult.FactoryForDeserialization, _sdkResolverService as INodePacketHandler); } @@ -398,6 +400,17 @@ private void OnNewConfigurationRequest(BuildRequestConfiguration config) } } + /// + /// Event handler for the BuildEngine's OnResourceRequest event. + /// + private void OnResourceRequest(ResourceRequest request) + { + if (_nodeEndpoint.LinkStatus == LinkStatus.Active) + { + _nodeEndpoint.SendData(request); + } + } + /// /// Event handler for the LoggingService's OnLoggingThreadException event. /// @@ -594,6 +607,10 @@ private void HandlePacket(INodePacket packet) HandleBuildRequestUnblocker(packet as BuildRequestUnblocker); break; + case NodePacketType.ResourceResponse: + HandleResourceResponse(packet as ResourceResponse); + break; + case NodePacketType.NodeConfiguration: HandleNodeConfiguration(packet as NodeConfiguration); break; @@ -636,6 +653,15 @@ private void HandleBuildRequestUnblocker(BuildRequestUnblocker unblocker) _buildRequestEngine.UnblockBuildRequest(unblocker); } + /// + /// Handles the ResourceResponse packet. + /// + /// + private void HandleResourceResponse(ResourceResponse response) + { + _buildRequestEngine.GrantResources(response); + } + /// /// Handles the NodeConfiguration packet. /// diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index e60efa1a52d..5ceb9c7fcc4 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -333,6 +333,8 @@ + + diff --git a/src/Shared/INodePacket.cs b/src/Shared/INodePacket.cs index 7ae2da0fc2e..501402c8756 100644 --- a/src/Shared/INodePacket.cs +++ b/src/Shared/INodePacket.cs @@ -174,9 +174,19 @@ internal enum NodePacketType : byte ResolveSdkRequest, /// - /// Message sent from back to a node when an SDK has been resolved. + /// Message sent back to a node when an SDK has been resolved. /// ResolveSdkResponse, + + /// + /// Message sent from a node when a task is requesting or returning resources from the scheduler. + /// + ResourceRequest, + + /// + /// Message sent back to a node informing it about the resource that were granted by the scheduler. + /// + ResourceResponse, } #endregion From 16538064d17ee5a871d06220905bf86efd1e20ac Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Mon, 22 Feb 2021 11:32:22 +0100 Subject: [PATCH 068/105] Add missing files: ResourceRequest.cs, ResourceResponse.cs --- src/Build/BackEnd/Shared/ResourceRequest.cs | 114 +++++++++++++++++++ src/Build/BackEnd/Shared/ResourceResponse.cs | 89 +++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/Build/BackEnd/Shared/ResourceRequest.cs create mode 100644 src/Build/BackEnd/Shared/ResourceResponse.cs diff --git a/src/Build/BackEnd/Shared/ResourceRequest.cs b/src/Build/BackEnd/Shared/ResourceRequest.cs new file mode 100644 index 00000000000..17556c5c87b --- /dev/null +++ b/src/Build/BackEnd/Shared/ResourceRequest.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; + +namespace Microsoft.Build.BackEnd +{ + /// + /// + internal class ResourceRequest : INodePacket + { + /// + /// The global request id of the request which is asking for resources. + /// + private int _blockedGlobalRequestId; + + private bool _isAcquire; + + private int _numCores; + + /// + /// Constructor for deserialization. + /// + internal ResourceRequest(ITranslator translator) + { + Translate(translator); + } + + /// + /// Acquire + /// + internal ResourceRequest(int blockedGlobalRequestId, int numCores) + { + _blockedGlobalRequestId = blockedGlobalRequestId; + _isAcquire = true; + _numCores = numCores; + } + + /// + /// Release + /// + internal ResourceRequest(int numCores) + { + _isAcquire = false; + _numCores = numCores; + } + + /// + /// Returns the type of packet. + /// + public NodePacketType Type + { + [DebuggerStepThrough] + get + { return NodePacketType.ResourceRequest; } + } + + /// + /// Accessor for the blocked request id. + /// + public int BlockedRequestId + { + [DebuggerStepThrough] + get + { + return _blockedGlobalRequestId; + } + } + + /// + /// + public bool IsAcquire + { + [DebuggerStepThrough] + get + { + return _isAcquire; + } + } + + /// + /// + public int NumCores + { + [DebuggerStepThrough] + get + { + return _numCores; + } + } + + #region INodePacketTranslatable Members + + /// + /// Serialization method. + /// + public void Translate(ITranslator translator) + { + translator.Translate(ref _blockedGlobalRequestId); + translator.Translate(ref _isAcquire); + translator.Translate(ref _numCores); + } + + #endregion + + /// + /// Factory for serialization. + /// + internal static INodePacket FactoryForDeserialization(ITranslator translator) + { + return new ResourceRequest(translator); + } + } +} diff --git a/src/Build/BackEnd/Shared/ResourceResponse.cs b/src/Build/BackEnd/Shared/ResourceResponse.cs new file mode 100644 index 00000000000..fddc805598b --- /dev/null +++ b/src/Build/BackEnd/Shared/ResourceResponse.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; + +namespace Microsoft.Build.BackEnd +{ + /// + /// + internal class ResourceResponse : INodePacket + { + /// + /// The global request id of the request which is being responded to. + /// + private int _blockedGlobalRequestId; + + private int _numCores; + + /// + /// Constructor for deserialization. + /// + internal ResourceResponse(ITranslator translator) + { + Translate(translator); + } + + /// + /// + internal ResourceResponse(int blockedGlobalRequestId, int numCores) + { + _blockedGlobalRequestId = blockedGlobalRequestId; + _numCores = numCores; + } + + /// + /// Returns the type of packet. + /// + public NodePacketType Type + { + [DebuggerStepThrough] + get + { return NodePacketType.ResourceResponse; } + } + + /// + /// Accessor for the blocked request id. + /// + public int BlockedRequestId + { + [DebuggerStepThrough] + get + { + return _blockedGlobalRequestId; + } + } + + /// + /// + public int NumCores + { + [DebuggerStepThrough] + get + { + return _numCores; + } + } + + #region INodePacketTranslatable Members + + /// + /// Serialization method. + /// + public void Translate(ITranslator translator) + { + translator.Translate(ref _blockedGlobalRequestId); + translator.Translate(ref _numCores); + } + + #endregion + + /// + /// Factory for serialization. + /// + internal static INodePacket FactoryForDeserialization(ITranslator translator) + { + return new ResourceResponse(translator); + } + } +} From 6d27a28389ef9e85d08269095f7b572669288613 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Wed, 24 Feb 2021 14:36:28 +0100 Subject: [PATCH 069/105] Plumbing fixes --- src/Build/BackEnd/BuildManager/BuildManager.cs | 5 +++-- .../Components/RequestBuilder/RequestBuilder.cs | 4 ++-- .../BackEnd/Components/Scheduler/IScheduler.cs | 4 ++-- src/Build/BackEnd/Shared/ResourceRequest.cs | 15 +++------------ 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index af19a40875c..684d45c86ed 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2178,14 +2178,15 @@ private void HandleResourceRequest(int node, ResourceRequest request) { if (request.IsAcquire) { - var coresAcquired = _scheduler.RequestCores(request.NumCores); + var coresAcquired = _scheduler.RequestCores(request.BlockedRequestId, request.NumCores); var response = new ResourceResponse(request.BlockedRequestId, coresAcquired ?? -1); _nodeManager.SendData(node, response); } else { - _scheduler.ReleaseCores(request.NumCores); + IEnumerable response = _scheduler.ReleaseCores(request.BlockedRequestId, request.NumCores); + PerformSchedulingActions(response); // No response needed. } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 0fcef27b0ff..6e473729003 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -496,7 +496,7 @@ public void ExitMSBuildCallbackState() public int? RequestCores(int requestedCores) { VerifyIsNotZombie(); - RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, requestedCores)); + RaiseResourceRequest(new ResourceRequest(true, _requestEntry.Request.GlobalRequestId, requestedCores)); WaitHandle[] handles = new WaitHandle[] { _terminateEvent, _continueWithResourcesEvent }; @@ -518,7 +518,7 @@ public void ExitMSBuildCallbackState() public void ReleaseCores(int coresToRelease) { VerifyIsNotZombie(); - RaiseResourceRequest(new ResourceRequest(coresToRelease)); + RaiseResourceRequest(new ResourceRequest(false, _requestEntry.Request.GlobalRequestId, coresToRelease)); } #endregion diff --git a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs index cc84d585988..d58fc3ffd72 100644 --- a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs @@ -72,11 +72,11 @@ internal interface IScheduler : IBuildComponent /// /// Requests CPU resources. /// - int? RequestCores(int requestCores); + int? RequestCores(int requestId, int requestedCores); /// /// Returns CPU resources. /// - void ReleaseCores(int coresToRelease); + List ReleaseCores(int requestId, int coresToRelease); } } diff --git a/src/Build/BackEnd/Shared/ResourceRequest.cs b/src/Build/BackEnd/Shared/ResourceRequest.cs index 17556c5c87b..b114bc7d546 100644 --- a/src/Build/BackEnd/Shared/ResourceRequest.cs +++ b/src/Build/BackEnd/Shared/ResourceRequest.cs @@ -27,21 +27,12 @@ internal ResourceRequest(ITranslator translator) } /// - /// Acquire + /// Constructor /// - internal ResourceRequest(int blockedGlobalRequestId, int numCores) + internal ResourceRequest(bool acquire, int blockedGlobalRequestId, int numCores) { + _isAcquire = acquire; _blockedGlobalRequestId = blockedGlobalRequestId; - _isAcquire = true; - _numCores = numCores; - } - - /// - /// Release - /// - internal ResourceRequest(int numCores) - { - _isAcquire = false; _numCores = numCores; } From df40170eb5f43a3d9101e86c5b960d639b0fc8fd Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Wed, 24 Feb 2021 14:37:20 +0100 Subject: [PATCH 070/105] Implement scheduling policy --- .../Scheduler/SchedulableRequest.cs | 10 +++++ .../BackEnd/Components/Scheduler/Scheduler.cs | 40 ++++++++++++++----- .../Components/Scheduler/SchedulingData.cs | 24 +++++++++-- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs b/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs index 590cc2c74a9..2382a4bcc3b 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs @@ -119,6 +119,11 @@ internal class SchedulableRequest /// private Dictionary _timeRecords; + /// + /// Number of cores requested as part of running the build request. + /// + private int _requestedCores; + /// /// Constructor. /// @@ -290,6 +295,11 @@ public DateTime EndTime } } + public int RequestedCores => _requestedCores; + + public void AddRequestedCores(int cores) => _requestedCores += cores; + public int RemoveRequestedCores(int cores) => _requestedCores = Math.Max(0, _requestedCores - cores); + /// /// Gets the amount of time we spent in the specified state. /// diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index b25c0595188..3c98d2fcac5 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -524,18 +524,26 @@ public void WriteDetailedSummary(int submissionId) /// /// Requests CPU resources. /// - public int? RequestCores(int requestCores) + public int? RequestCores(int requestId, int requestedCores) { - // TODO: ladipro - return null; + int grantedCores = Math.Min(requestedCores, GetAvailableCores()); + SchedulableRequest request = _schedulingData.GetExecutingRequest(requestId); + request.AddRequestedCores(grantedCores); + return grantedCores; } /// /// Returns CPU resources. /// - public void ReleaseCores(int coresToRelease) + public List ReleaseCores(int requestId, int coresToRelease) { - // TODO: ladipro + SchedulableRequest request = _schedulingData.GetExecutingRequest(requestId); + request.RemoveRequestedCores(coresToRelease); + + // Releasing cores means that we may be able to schedule more work. + List responses = new List(); + ScheduleUnassignedRequests(responses); + return responses; } #endregion @@ -1292,14 +1300,11 @@ private void AssignUnscheduledRequestToNode(SchedulableRequest request, int node request.ResumeExecution(nodeId); } - /// - /// Returns true if we are at the limit of work we can schedule. - /// - private bool AtSchedulingLimit() + private int GetAvailableCores() { if (_schedulingUnlimited) { - return false; + return int.MaxValue; } int limit = _componentHost.BuildParameters.MaxNodeCount switch @@ -1309,11 +1314,24 @@ private bool AtSchedulingLimit() _ => _componentHost.BuildParameters.MaxNodeCount + 2 + _nodeLimitOffset, }; + return Math.Max(0, limit - (_schedulingData.ExecutingRequestsCount + _schedulingData.ExplicitlyRequestedCores + _schedulingData.YieldingRequestsCount)); + } + + /// + /// Returns true if we are at the limit of work we can schedule. + /// + private bool AtSchedulingLimit() + { + if (_schedulingUnlimited) + { + return false; + } + // We're at our limit of schedulable requests if: // (1) MaxNodeCount requests are currently executing // (2) Fewer than MaxNodeCount requests are currently executing but the sum of executing // and yielding requests exceeds the limit set out above. - return _schedulingData.ExecutingRequestsCount + _schedulingData.YieldingRequestsCount >= limit || + return GetAvailableCores() == 0 || _schedulingData.ExecutingRequestsCount >= _componentHost.BuildParameters.MaxNodeCount; } diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs index 804ac117b8a..a7b7f200122 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Microsoft.Build.Shared; using Microsoft.Build.Collections; +using System.Linq; namespace Microsoft.Build.BackEnd { @@ -152,6 +153,14 @@ public int ReadyRequestsCount get { return _readyRequests.Count; } } + /// + /// Gets the total number of cores requested by executing and yielding build requests. + /// + public int ExplicitlyRequestedCores + { + get { return _executingRequests.Sum(kvp => kvp.Value.RequestedCores) + _yieldingRequests.Sum(kvp => kvp.Value.RequestedCores); } + } + /// /// Retrieves all of the blocked requests. /// @@ -492,12 +501,21 @@ public SchedulableRequest GetScheduledRequest(int globalRequestId) public bool IsNodeWorking(int nodeId) { SchedulableRequest request; - if (!_executingRequestByNode.TryGetValue(nodeId, out request)) + if (_executingRequestByNode.TryGetValue(nodeId, out request) && request != null) { - return false; + return true; } - return request != null; + foreach (KeyValuePair kvp in _yieldingRequests) + { + if (kvp.Value.AssignedNode == nodeId && kvp.Value.RequestedCores > 0) + { + // This node does not have an executing task on it. However, it does have a yielding task + // that has explicitly asked for cores which makes it "working". + return true; + } + } + return false; } /// From 3d03f8634af3b898c2495b1db65ca663c6a6f0cb Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Mon, 1 Mar 2021 09:09:47 +0100 Subject: [PATCH 071/105] Use 'implicit core' always, not only when scheduler returns 0 --- .../BackEnd/Components/RequestBuilder/TaskHost.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index c961eb9db8b..99f6159d124 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -689,19 +689,18 @@ public void LogTelemetry(string eventName, IDictionary propertie lock (_callbackMonitor) { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; - var coresAcquired = builderCallback.RequestCores(requestedCores); + var coresAcquired = builderCallback.RequestCores(implicitCoreUsed ? requestedCores : requestedCores - 1); if (coresAcquired.HasValue) { runningTotal += coresAcquired.Value; } - if (!implicitCoreUsed && coresAcquired == 0) + if (!implicitCoreUsed) { - // If we got nothing back from the actual system, pad it with the one implicit core - // you get just for running--that way we never block and always return > 1 + // Always factor in the implicit core assigned to the node running this task. implicitCoreUsed = true; - coresAcquired = 1; + return coresAcquired + 1; } return coresAcquired; @@ -712,7 +711,7 @@ public void ReleaseCores(int coresToRelease) { lock (_callbackMonitor) { - if (implicitCoreUsed) + if (coresToRelease > 0 && implicitCoreUsed) { coresToRelease -= 1; implicitCoreUsed = false; From ba264dafe86e254a6d5497f5c12b7082a9fb5f09 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Mon, 1 Mar 2021 10:10:52 +0100 Subject: [PATCH 072/105] Use different limits for scheduling and explicit core requests --- .../BackEnd/Components/Scheduler/Scheduler.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 3c98d2fcac5..933ada4a7a3 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -72,7 +72,10 @@ internal class Scheduler : IScheduler /// private int _nodeLimitOffset; - // private int _resourceManagedCoresUsed = 0; + /// + /// NativeMethodsShared.GetLogicalCoreCount() or MSBUILDCORELIMIT if set + /// + private int _coreLimit; /// /// { nodeId -> NodeInfo } @@ -186,6 +189,11 @@ public Scheduler() } } + if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDCORELIMIT"), out _coreLimit) || _coreLimit <= 0) + { + _coreLimit = NativeMethodsShared.GetLogicalCoreCount(); + } + if (String.IsNullOrEmpty(_debugDumpPath)) { _debugDumpPath = Path.GetTempPath(); @@ -526,7 +534,7 @@ public void WriteDetailedSummary(int submissionId) /// public int? RequestCores(int requestId, int requestedCores) { - int grantedCores = Math.Min(requestedCores, GetAvailableCores()); + int grantedCores = Math.Min(requestedCores, GetAvailableCoresForExplicitRequests()); SchedulableRequest request = _schedulingData.GetExecutingRequest(requestId); request.AddRequestedCores(grantedCores); return grantedCores; @@ -1300,7 +1308,7 @@ private void AssignUnscheduledRequestToNode(SchedulableRequest request, int node request.ResumeExecution(nodeId); } - private int GetAvailableCores() + private int GetAvailableCoresForScheduling() { if (_schedulingUnlimited) { @@ -1317,6 +1325,11 @@ private int GetAvailableCores() return Math.Max(0, limit - (_schedulingData.ExecutingRequestsCount + _schedulingData.ExplicitlyRequestedCores + _schedulingData.YieldingRequestsCount)); } + private int GetAvailableCoresForExplicitRequests() + { + return Math.Max(0, _coreLimit - (_schedulingData.ExecutingRequestsCount + _schedulingData.ExplicitlyRequestedCores)); + } + /// /// Returns true if we are at the limit of work we can schedule. /// @@ -1331,7 +1344,7 @@ private bool AtSchedulingLimit() // (1) MaxNodeCount requests are currently executing // (2) Fewer than MaxNodeCount requests are currently executing but the sum of executing // and yielding requests exceeds the limit set out above. - return GetAvailableCores() == 0 || + return GetAvailableCoresForScheduling() == 0 || _schedulingData.ExecutingRequestsCount >= _componentHost.BuildParameters.MaxNodeCount; } From d12bd75294561985613d2330f2ed0777cd0306b3 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 5 Mar 2021 17:35:10 +0100 Subject: [PATCH 073/105] Make RequestCores ignore the executing request count --- src/Build/BackEnd/Components/Scheduler/Scheduler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 933ada4a7a3..a6e3c3d6eb5 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -1327,7 +1327,7 @@ private int GetAvailableCoresForScheduling() private int GetAvailableCoresForExplicitRequests() { - return Math.Max(0, _coreLimit - (_schedulingData.ExecutingRequestsCount + _schedulingData.ExplicitlyRequestedCores)); + return Math.Max(0, _coreLimit - (/*_schedulingData.ExecutingRequestsCount +*/ _schedulingData.ExplicitlyRequestedCores)); } /// From 1a6326e936e77f1fa59d538e2a1a8de57b65fb4a Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 5 Mar 2021 17:48:56 +0100 Subject: [PATCH 074/105] Subtract one from _coreLimit --- src/Build/BackEnd/Components/Scheduler/Scheduler.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index a6e3c3d6eb5..d1d318ba3ce 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -193,6 +193,9 @@ public Scheduler() { _coreLimit = NativeMethodsShared.GetLogicalCoreCount(); } + // Tasks are factoring in the "implicit core" so let's make the maximum return value from + // RequestCore exactly the number of cores. + _coreLimit = Math.Max(0, _coreLimit - 1); if (String.IsNullOrEmpty(_debugDumpPath)) { From 0b1a48769ba9f9b1d6d7e374d8bf3710819fda4c Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 12 Mar 2021 16:38:09 +0100 Subject: [PATCH 075/105] Change return value of RequestCores to int (null is no longer used) --- src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs | 4 ++-- src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs | 4 ++-- src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs | 4 ++-- src/Build.UnitTests/BackEnd/TaskHost_Tests.cs | 4 ++-- src/Build/BackEnd/BuildManager/BuildManager.cs | 2 +- .../Components/RequestBuilder/IRequestBuilderCallback.cs | 2 +- .../BackEnd/Components/RequestBuilder/RequestBuilder.cs | 2 +- .../BackEnd/Components/RequestBuilder/TargetBuilder.cs | 2 +- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 7 ++----- .../Components/ResourceManager/ResourceManagerService.cs | 4 ++-- src/Build/BackEnd/Components/Scheduler/IScheduler.cs | 2 +- src/Build/BackEnd/Components/Scheduler/Scheduler.cs | 2 +- src/Framework/IBuildEngine8.cs | 2 +- src/MSBuild/OutOfProcTaskHostNode.cs | 9 +++++---- src/Shared/UnitTests/MockEngine.cs | 4 ++-- 15 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs index 6f62bba78eb..4d94d6bd0f6 100644 --- a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs @@ -1420,9 +1420,9 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Empty impl /// - int? IRequestBuilderCallback.RequestCores(int requestedCores) + int IRequestBuilderCallback.RequestCores(int requestedCores) { - return null; + return 0; } /// diff --git a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs index 4688c52349f..d95ecd20798 100644 --- a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs @@ -981,9 +981,9 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Empty impl /// - int? IRequestBuilderCallback.RequestCores(int requestedCores) + int IRequestBuilderCallback.RequestCores(int requestedCores) { - return null; + return 0; } /// diff --git a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs index b5200cf78a5..c9a5cc960ee 100644 --- a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs @@ -758,9 +758,9 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Empty impl /// - int? IRequestBuilderCallback.RequestCores(int requestedCores) + int IRequestBuilderCallback.RequestCores(int requestedCores) { - return null; + return 0; } /// diff --git a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs index 5fd7ac4624c..1a9c112b44f 100644 --- a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs @@ -1302,9 +1302,9 @@ public void ExitMSBuildCallbackState() /// /// Mock /// - public int? RequestCores(int requestedCores) + public int RequestCores(int requestedCores) { - return null; + return 0; } /// diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 684d45c86ed..cd59ba32a8b 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2179,7 +2179,7 @@ private void HandleResourceRequest(int node, ResourceRequest request) if (request.IsAcquire) { var coresAcquired = _scheduler.RequestCores(request.BlockedRequestId, request.NumCores); - var response = new ResourceResponse(request.BlockedRequestId, coresAcquired ?? -1); + var response = new ResourceResponse(request.BlockedRequestId, coresAcquired); _nodeManager.SendData(node, response); } diff --git a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs index bdf7a6af7d8..2089685fce3 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs @@ -61,7 +61,7 @@ internal interface IRequestBuilderCallback /// /// Number of logical cores being requested. /// Number of logical cores actually granted. - int? RequestCores(int requestedCores); + int RequestCores(int requestedCores); /// /// Returns CPU resources to the scheduler. diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 6e473729003..b9348df003d 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -493,7 +493,7 @@ public void ExitMSBuildCallbackState() /// /// Requests CPU resources from the scheduler. /// - public int? RequestCores(int requestedCores) + public int RequestCores(int requestedCores) { VerifyIsNotZombie(); RaiseResourceRequest(new ResourceRequest(true, _requestEntry.Request.GlobalRequestId, requestedCores)); diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs index 13f6ce418ff..dde2589ae00 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs @@ -366,7 +366,7 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Requests CPU resources from the scheduler. /// - int? IRequestBuilderCallback.RequestCores(int requestedCores) + int IRequestBuilderCallback.RequestCores(int requestedCores) { return _requestBuilderCallback.RequestCores(requestedCores); } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 99f6159d124..1681cd0eca4 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -684,17 +684,14 @@ public void LogTelemetry(string eventName, IDictionary propertie int runningTotal = 0; bool implicitCoreUsed = false; - public int? RequestCores(int requestedCores) + public int RequestCores(int requestedCores) { lock (_callbackMonitor) { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; var coresAcquired = builderCallback.RequestCores(implicitCoreUsed ? requestedCores : requestedCores - 1); - if (coresAcquired.HasValue) - { - runningTotal += coresAcquired.Value; - } + runningTotal += coresAcquired; if (!implicitCoreUsed) { diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs index cee32b41ff9..10934d2fbbc 100644 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs @@ -37,9 +37,9 @@ public void ShutdownComponent() TotalNumberHeld = -2; } - public int? RequestCores(int requestedCores, TaskLoggingContext _taskLoggingContext) + public int RequestCores(int requestedCores, TaskLoggingContext _taskLoggingContext) { - return null; + return 0; // _loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerRequestedCores", requestedCores, i, TotalNumberHeld); } diff --git a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs index d58fc3ffd72..a97e829a156 100644 --- a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs @@ -72,7 +72,7 @@ internal interface IScheduler : IBuildComponent /// /// Requests CPU resources. /// - int? RequestCores(int requestId, int requestedCores); + int RequestCores(int requestId, int requestedCores); /// /// Returns CPU resources. diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index d1d318ba3ce..bb11ea2b48c 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -535,7 +535,7 @@ public void WriteDetailedSummary(int submissionId) /// /// Requests CPU resources. /// - public int? RequestCores(int requestId, int requestedCores) + public int RequestCores(int requestId, int requestedCores) { int grantedCores = Math.Min(requestedCores, GetAvailableCoresForExplicitRequests()); SchedulableRequest request = _schedulingData.GetExecutingRequest(requestId); diff --git a/src/Framework/IBuildEngine8.cs b/src/Framework/IBuildEngine8.cs index 85e61dfc7c2..5c73b072a75 100644 --- a/src/Framework/IBuildEngine8.cs +++ b/src/Framework/IBuildEngine8.cs @@ -14,7 +14,7 @@ public interface IBuildEngine8 : IBuildEngine7 /// /// The number of cores a task can potentially use. /// The number of cores a task is allowed to use. - int? RequestCores(int requestedCores); + int RequestCores(int requestedCores); /// /// A task should notify the build manager when all or some of the requested cores are not used anymore. diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index a4e1a08f878..8dfe59d057b 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -1162,15 +1162,16 @@ private void LogErrorFromResource(string messageResource) LogErrorEvent(error); } - public int? RequestCores(int requestedCores) + public int RequestCores(int requestedCores) { - // indicate to caller that resource management isn't hooked up - return null; + // No resource management in OOP nodes + throw new NotImplementedException(); } public void ReleaseCores(int coresToRelease) { - // Do nothing: no resource management in OOP nodes + // No resource management in OOP nodes + throw new NotImplementedException(); } } } diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index dd8d06ca6ec..ac6ffee81cf 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -491,9 +491,9 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life return obj; } - public int? RequestCores(int requestedCores) + public int RequestCores(int requestedCores) { - return null; + return 0; } public void ReleaseCores(int coresToRelease) From d38500b1e0c9505808591aa1494a8ca56013dccb Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 12 Mar 2021 16:42:22 +0100 Subject: [PATCH 076/105] Do not assume that RequestCores calls come only from Executing requests --- src/Build/BackEnd/Components/Scheduler/Scheduler.cs | 2 +- src/Build/BackEnd/Components/Scheduler/SchedulingData.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index bb11ea2b48c..c1d78db318a 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -538,7 +538,7 @@ public void WriteDetailedSummary(int submissionId) public int RequestCores(int requestId, int requestedCores) { int grantedCores = Math.Min(requestedCores, GetAvailableCoresForExplicitRequests()); - SchedulableRequest request = _schedulingData.GetExecutingRequest(requestId); + SchedulableRequest request = _schedulingData.GetScheduledRequest(requestId); request.AddRequestedCores(grantedCores); return grantedCores; } diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs index a7b7f200122..99512373e62 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs @@ -486,7 +486,7 @@ public SchedulableRequest GetReadyRequest(int globalRequestId) } /// - /// Retrieves a request which has been assigned to a node and is in the executing, blocked or ready states. + /// Retrieves a request which has been assigned to a node and is in the executing, yielding, blocked, ready states. /// public SchedulableRequest GetScheduledRequest(int globalRequestId) { From 445ec333ddf0d07d93a89995324f2c23a4e286d1 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Tue, 16 Mar 2021 13:13:02 +0100 Subject: [PATCH 077/105] Make RequestCores block and wait for at least one core --- .../BackEnd/BuildManager/BuildManager.cs | 9 ++-- .../RequestBuilder/RequestBuilder.cs | 2 - .../Components/Scheduler/IScheduler.cs | 3 +- .../BackEnd/Components/Scheduler/Scheduler.cs | 54 ++++++++++++++++--- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index cd59ba32a8b..e9714427c50 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2178,10 +2178,11 @@ private void HandleResourceRequest(int node, ResourceRequest request) { if (request.IsAcquire) { - var coresAcquired = _scheduler.RequestCores(request.BlockedRequestId, request.NumCores); - var response = new ResourceResponse(request.BlockedRequestId, coresAcquired); - - _nodeManager.SendData(node, response); + _scheduler.RequestCores(request.BlockedRequestId, request.NumCores).ContinueWith((Task task) => + { + var response = new ResourceResponse(request.BlockedRequestId, task.Result); + _nodeManager.SendData(node, response); + }, TaskContinuationOptions.ExecuteSynchronously); } else { diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index b9348df003d..bb53debf53e 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.BackEnd.SdkResolution; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Eventing; @@ -19,7 +18,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Build.Experimental.ProjectCache; using NodeLoggingContext = Microsoft.Build.BackEnd.Logging.NodeLoggingContext; using ProjectLoggingContext = Microsoft.Build.BackEnd.Logging.ProjectLoggingContext; diff --git a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs index a97e829a156..0e8d516072e 100644 --- a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.Build.Execution; namespace Microsoft.Build.BackEnd @@ -72,7 +73,7 @@ internal interface IScheduler : IBuildComponent /// /// Requests CPU resources. /// - int RequestCores(int requestId, int requestedCores); + Task RequestCores(int requestId, int requestedCores); /// /// Returns CPU resources. diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index c1d78db318a..84c65c34eb6 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -9,7 +9,7 @@ using System.Linq; using System.Text; using System.Threading; - +using System.Threading.Tasks; using Microsoft.Build.BackEnd.Components.ResourceManager; using Microsoft.Build.Execution; using Microsoft.Build.Framework; @@ -101,6 +101,11 @@ internal class Scheduler : IScheduler /// private SchedulingData _schedulingData; + /// + /// A queue of RequestCores request waiting for at least one core to become available. + /// + private Queue> _pendingRequestCoresCallbacks = new Queue>(); + #endregion /// @@ -502,6 +507,7 @@ public void Reset() DumpRequests(); _schedulingPlan = null; _schedulingData = new SchedulingData(); + _pendingRequestCoresCallbacks = new Queue>(); _availableNodes = new Dictionary(8); _currentInProcNodeCount = 0; _currentOutOfProcNodeCount = 0; @@ -535,12 +541,36 @@ public void WriteDetailedSummary(int submissionId) /// /// Requests CPU resources. /// - public int RequestCores(int requestId, int requestedCores) + public Task RequestCores(int requestId, int requestedCores) { - int grantedCores = Math.Min(requestedCores, GetAvailableCoresForExplicitRequests()); - SchedulableRequest request = _schedulingData.GetScheduledRequest(requestId); - request.AddRequestedCores(grantedCores); - return grantedCores; + if (requestedCores == 0) + { + return Task.FromResult(0); + } + + Func grantCores = (int availableCores) => + { + int grantedCores = Math.Min(requestedCores, availableCores); + if (grantedCores > 0) + { + SchedulableRequest request = _schedulingData.GetScheduledRequest(requestId); + request.AddRequestedCores(grantedCores); + } + return grantedCores; + }; + + int grantedCores = grantCores(GetAvailableCoresForExplicitRequests()); + if (grantedCores > 0) + { + return Task.FromResult(grantedCores); + } + else + { + // We have no cores to grant at the moment, queue up the request. + TaskCompletionSource completionSource = new TaskCompletionSource(); + _pendingRequestCoresCallbacks.Enqueue(completionSource); + return completionSource.Task.ContinueWith((Task task) => grantCores(task.Result), TaskContinuationOptions.ExecuteSynchronously); + } } /// @@ -1780,6 +1810,18 @@ private void ResolveRequestFromCacheAndResumeIfPossible(SchedulableRequest reque /// private void ResumeRequiredWork(List responses) { + // If we have pending RequestCore calls, satisfy those first. + while (_pendingRequestCoresCallbacks.Count > 0) + { + int availableCores = GetAvailableCoresForExplicitRequests(); + if (availableCores == 0) + { + break; + } + TaskCompletionSource completionSource = _pendingRequestCoresCallbacks.Dequeue(); + completionSource.SetResult(availableCores); + } + // Resume any ready requests on the existing nodes. foreach (int nodeId in _availableNodes.Keys) { From a98adee78d0ad9a557669214aef09df8834aa0ab Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Tue, 16 Mar 2021 13:14:46 +0100 Subject: [PATCH 078/105] Revert "Use 'implicit core' always, not only when scheduler returns 0" This reverts commit 3d03f8634af3b898c2495b1db65ca663c6a6f0cb. --- .../BackEnd/Components/RequestBuilder/TaskHost.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 1681cd0eca4..1fb9626d961 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -689,15 +689,16 @@ public int RequestCores(int requestedCores) lock (_callbackMonitor) { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; - var coresAcquired = builderCallback.RequestCores(implicitCoreUsed ? requestedCores : requestedCores - 1); + var coresAcquired = builderCallback.RequestCores(requestedCores); runningTotal += coresAcquired; - if (!implicitCoreUsed) + if (!implicitCoreUsed && coresAcquired == 0) { - // Always factor in the implicit core assigned to the node running this task. + // If we got nothing back from the actual system, pad it with the one implicit core + // you get just for running--that way we never block and always return > 1 implicitCoreUsed = true; - return coresAcquired + 1; + coresAcquired = 1; } return coresAcquired; @@ -708,7 +709,7 @@ public void ReleaseCores(int coresToRelease) { lock (_callbackMonitor) { - if (coresToRelease > 0 && implicitCoreUsed) + if (implicitCoreUsed) { coresToRelease -= 1; implicitCoreUsed = false; From 15121becd7b62f9a67952072efaee82a66305f79 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Tue, 16 Mar 2021 13:52:33 +0100 Subject: [PATCH 079/105] Make the first RequestCores call non-blocking (via 'implicit' core) --- .../BackEnd/TargetBuilder_Tests.cs | 2 +- .../BackEnd/TargetEntry_Tests.cs | 2 +- .../BackEnd/TaskBuilder_Tests.cs | 2 +- src/Build.UnitTests/BackEnd/TaskHost_Tests.cs | 2 +- .../BackEnd/BuildManager/BuildManager.cs | 2 +- .../RequestBuilder/IRequestBuilderCallback.cs | 6 ++-- .../RequestBuilder/RequestBuilder.cs | 6 ++-- .../RequestBuilder/TargetBuilder.cs | 4 +-- .../Components/RequestBuilder/TaskHost.cs | 22 +++++++++---- .../Components/Scheduler/IScheduler.cs | 2 +- .../BackEnd/Components/Scheduler/Scheduler.cs | 4 +-- src/Build/BackEnd/Shared/ResourceRequest.cs | 31 +++++++++++++++++-- 12 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs index 4d94d6bd0f6..2d9b1f3a9e7 100644 --- a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs @@ -1420,7 +1420,7 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Empty impl /// - int IRequestBuilderCallback.RequestCores(int requestedCores) + int IRequestBuilderCallback.RequestCores(int requestedCores, bool waitForCores) { return 0; } diff --git a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs index d95ecd20798..6d73dcd4bed 100644 --- a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs @@ -981,7 +981,7 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Empty impl /// - int IRequestBuilderCallback.RequestCores(int requestedCores) + int IRequestBuilderCallback.RequestCores(int requestedCores, bool waitForCores) { return 0; } diff --git a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs index c9a5cc960ee..2bf9f56669f 100644 --- a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs @@ -758,7 +758,7 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Empty impl /// - int IRequestBuilderCallback.RequestCores(int requestedCores) + int IRequestBuilderCallback.RequestCores(int requestedCores, bool waitForCores) { return 0; } diff --git a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs index 1a9c112b44f..a4c790a2209 100644 --- a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs @@ -1302,7 +1302,7 @@ public void ExitMSBuildCallbackState() /// /// Mock /// - public int RequestCores(int requestedCores) + public int RequestCores(int requestedCores, bool waitForCores) { return 0; } diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index e9714427c50..5cb3b81dc0b 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2178,7 +2178,7 @@ private void HandleResourceRequest(int node, ResourceRequest request) { if (request.IsAcquire) { - _scheduler.RequestCores(request.BlockedRequestId, request.NumCores).ContinueWith((Task task) => + _scheduler.RequestCores(request.BlockedRequestId, request.NumCores, request.IsBlocking).ContinueWith((Task task) => { var response = new ResourceResponse(request.BlockedRequestId, task.Result); _nodeManager.SendData(node, response); diff --git a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs index 2089685fce3..323f046e040 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs @@ -60,8 +60,10 @@ internal interface IRequestBuilderCallback /// Requests CPU resources from the scheduler. /// /// Number of logical cores being requested. - /// Number of logical cores actually granted. - int RequestCores(int requestedCores); + /// True to make the request block and wait for at least one core. + /// Number of logical cores actually granted. If is false, the call can return + /// zero. Otherwise the return value is always positive. + int RequestCores(int requestedCores, bool waitForCores); /// /// Returns CPU resources to the scheduler. diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index bb53debf53e..e4f942f26fd 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -491,10 +491,10 @@ public void ExitMSBuildCallbackState() /// /// Requests CPU resources from the scheduler. /// - public int RequestCores(int requestedCores) + public int RequestCores(int requestedCores, bool waitForCores) { VerifyIsNotZombie(); - RaiseResourceRequest(new ResourceRequest(true, _requestEntry.Request.GlobalRequestId, requestedCores)); + RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, requestedCores, waitForCores)); WaitHandle[] handles = new WaitHandle[] { _terminateEvent, _continueWithResourcesEvent }; @@ -516,7 +516,7 @@ public int RequestCores(int requestedCores) public void ReleaseCores(int coresToRelease) { VerifyIsNotZombie(); - RaiseResourceRequest(new ResourceRequest(false, _requestEntry.Request.GlobalRequestId, coresToRelease)); + RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, coresToRelease)); } #endregion diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs index dde2589ae00..6e63766bc8f 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs @@ -366,9 +366,9 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Requests CPU resources from the scheduler. /// - int IRequestBuilderCallback.RequestCores(int requestedCores) + int IRequestBuilderCallback.RequestCores(int requestedCores, bool waitForCores) { - return _requestBuilderCallback.RequestCores(requestedCores); + return _requestBuilderCallback.RequestCores(requestedCores, waitForCores); } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 1fb9626d961..afa56e13052 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -688,19 +688,29 @@ public int RequestCores(int requestedCores) { lock (_callbackMonitor) { - IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; - var coresAcquired = builderCallback.RequestCores(requestedCores); + int coresAcquired = 0; + IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; + if (implicitCoreUsed) + { + coresAcquired = builderCallback.RequestCores(requestedCores, waitForCores: true); + } + else if (requestedCores > 1) + { + coresAcquired = builderCallback.RequestCores(requestedCores - 1, waitForCores: false); + } runningTotal += coresAcquired; - if (!implicitCoreUsed && coresAcquired == 0) + if (!implicitCoreUsed) { // If we got nothing back from the actual system, pad it with the one implicit core - // you get just for running--that way we never block and always return > 1 + // you get just for running--that way the first call never blocks and always returns >= 1 implicitCoreUsed = true; - coresAcquired = 1; + coresAcquired++; } + Debug.Assert(coresAcquired >= 1); + return coresAcquired; } } @@ -726,7 +736,7 @@ public void ReleaseCores(int coresToRelease) internal void ReleaseAllCores() { - ReleaseCores(runningTotal); + ReleaseCores(runningTotal + (implicitCoreUsed ? 1 : 0)); runningTotal = 0; implicitCoreUsed = false; diff --git a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs index 0e8d516072e..de90369fabb 100644 --- a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs @@ -73,7 +73,7 @@ internal interface IScheduler : IBuildComponent /// /// Requests CPU resources. /// - Task RequestCores(int requestId, int requestedCores); + Task RequestCores(int requestId, int requestedCores, bool waitForCores); /// /// Returns CPU resources. diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 84c65c34eb6..c4169bf8724 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -541,7 +541,7 @@ public void WriteDetailedSummary(int submissionId) /// /// Requests CPU resources. /// - public Task RequestCores(int requestId, int requestedCores) + public Task RequestCores(int requestId, int requestedCores, bool waitForCores) { if (requestedCores == 0) { @@ -560,7 +560,7 @@ public Task RequestCores(int requestId, int requestedCores) }; int grantedCores = grantCores(GetAvailableCoresForExplicitRequests()); - if (grantedCores > 0) + if (grantedCores > 0 || !waitForCores) { return Task.FromResult(grantedCores); } diff --git a/src/Build/BackEnd/Shared/ResourceRequest.cs b/src/Build/BackEnd/Shared/ResourceRequest.cs index b114bc7d546..01abe649217 100644 --- a/src/Build/BackEnd/Shared/ResourceRequest.cs +++ b/src/Build/BackEnd/Shared/ResourceRequest.cs @@ -16,6 +16,8 @@ internal class ResourceRequest : INodePacket private bool _isAcquire; + private bool _isBlocking; + private int _numCores; /// @@ -27,11 +29,22 @@ internal ResourceRequest(ITranslator translator) } /// - /// Constructor + /// Constructor for acquiring. /// - internal ResourceRequest(bool acquire, int blockedGlobalRequestId, int numCores) + internal ResourceRequest(int blockedGlobalRequestId, int numCores, bool isBlocking) { - _isAcquire = acquire; + _isAcquire = true; + _isBlocking = isBlocking; + _blockedGlobalRequestId = blockedGlobalRequestId; + _numCores = numCores; + } + + /// + /// Constructor for releasing. + /// + internal ResourceRequest(int blockedGlobalRequestId, int numCores) + { + _isAcquire = false; _blockedGlobalRequestId = blockedGlobalRequestId; _numCores = numCores; } @@ -69,6 +82,17 @@ public bool IsAcquire } } + /// + /// + public bool IsBlocking + { + [DebuggerStepThrough] + get + { + return _isBlocking; + } + } + /// /// public int NumCores @@ -89,6 +113,7 @@ public void Translate(ITranslator translator) { translator.Translate(ref _blockedGlobalRequestId); translator.Translate(ref _isAcquire); + translator.Translate(ref _isBlocking); translator.Translate(ref _numCores); } From 02bce34ffc21a4978c2459763772054af5522be8 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Tue, 16 Mar 2021 14:09:16 +0100 Subject: [PATCH 080/105] Make the implicit core the last one to release (LIFO) --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index afa56e13052..817835c893b 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -719,7 +719,7 @@ public void ReleaseCores(int coresToRelease) { lock (_callbackMonitor) { - if (implicitCoreUsed) + if (implicitCoreUsed && coresToRelease > runningTotal) { coresToRelease -= 1; implicitCoreUsed = false; From b382aab5e32bdb6497408e9553d37ae22c3e0753 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Tue, 16 Mar 2021 16:41:50 +0100 Subject: [PATCH 081/105] Introduce MSBUILDNODECOREALLOCATIONWEIGHT --- .../BackEnd/Components/Scheduler/Scheduler.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index c4169bf8724..54069122a58 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -77,6 +77,11 @@ internal class Scheduler : IScheduler /// private int _coreLimit; + /// + /// The weight of busy nodes in GetAvailableCoresForExplicitRequests(). + /// + private int _nodeCoreAllocationWeight; + /// /// { nodeId -> NodeInfo } /// A list of nodes we know about. For the non-distributed case, there will be no more nodes than the @@ -202,6 +207,13 @@ public Scheduler() // RequestCore exactly the number of cores. _coreLimit = Math.Max(0, _coreLimit - 1); + if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDNODECOREALLOCATIONWEIGHT"), out _nodeCoreAllocationWeight) + || _nodeCoreAllocationWeight <= 0 + || _nodeCoreAllocationWeight > 100) + { + _nodeCoreAllocationWeight = 0; + } + if (String.IsNullOrEmpty(_debugDumpPath)) { _debugDumpPath = Path.GetTempPath(); @@ -1360,7 +1372,9 @@ private int GetAvailableCoresForScheduling() private int GetAvailableCoresForExplicitRequests() { - return Math.Max(0, _coreLimit - (/*_schedulingData.ExecutingRequestsCount +*/ _schedulingData.ExplicitlyRequestedCores)); + int implicitlyAllocatedCores = ((_schedulingData.ExecutingRequestsCount - 1) * _nodeCoreAllocationWeight) / 100; + int explicitlyAllocatedCores = _schedulingData.ExplicitlyRequestedCores; + return Math.Max(0, _coreLimit - (implicitlyAllocatedCores + explicitlyAllocatedCores)); } /// From 14c5d3d5d7208f31c2eb7c2e5ee135e1105c12ca Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Tue, 23 Mar 2021 21:57:59 +0100 Subject: [PATCH 082/105] Allow calling RequestCores/ReleaseCores after Yield --- .../Components/RequestBuilder/RequestBuilder.cs | 13 +++++++++++-- src/Build/BackEnd/Components/Scheduler/Scheduler.cs | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index e4f942f26fd..3ac8c38a1c0 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -242,7 +242,7 @@ public void ContinueRequestWithResources(ResourceResponse response) ErrorUtilities.VerifyThrow(HasActiveBuildRequest, "Request not building"); ErrorUtilities.VerifyThrow(!_terminateEvent.WaitOne(0), "Request already terminated"); ErrorUtilities.VerifyThrow(!_continueWithResourcesEvent.WaitOne(0), "Request already continued"); - VerifyEntryInActiveState(); + VerifyEntryInActiveOrWaitingState(); _continueResources = response; _continueWithResourcesEvent.Set(); @@ -506,7 +506,7 @@ public int RequestCores(int requestedCores, bool waitForCores) throw new BuildAbortedException(); } - VerifyEntryInActiveState(); + VerifyEntryInActiveOrWaitingState(); return _continueResources.NumCores; } @@ -735,6 +735,15 @@ private void VerifyEntryInActiveState() ErrorUtilities.VerifyThrow(_requestEntry.State == BuildRequestEntryState.Active, "Entry is not in the Active state, it is in the {0} state.", _requestEntry.State); } + /// + /// Asserts that the entry is in the active or waiting state. + /// + private void VerifyEntryInActiveOrWaitingState() + { + ErrorUtilities.VerifyThrow(_requestEntry.State == BuildRequestEntryState.Active || _requestEntry.State == BuildRequestEntryState.Waiting, + "Entry is not in the Active state, it is in the {0} state.", _requestEntry.State); + } + /// /// The entry point for the request builder thread. /// diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 54069122a58..c5a60da817b 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -590,7 +590,7 @@ public Task RequestCores(int requestId, int requestedCores, bool waitForCor /// public List ReleaseCores(int requestId, int coresToRelease) { - SchedulableRequest request = _schedulingData.GetExecutingRequest(requestId); + SchedulableRequest request = _schedulingData.GetScheduledRequest(requestId); request.RemoveRequestedCores(coresToRelease); // Releasing cores means that we may be able to schedule more work. From aad6f76b1717b226d8ed49bb21704412c7472569 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Wed, 24 Mar 2021 12:46:49 +0100 Subject: [PATCH 083/105] Update resource-management.md --- documentation/specs/resource-management.md | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/documentation/specs/resource-management.md b/documentation/specs/resource-management.md index 7b84e50efc9..92343be73a4 100644 --- a/documentation/specs/resource-management.md +++ b/documentation/specs/resource-management.md @@ -10,17 +10,17 @@ As a result, the standard guidance is to use only one multiproc option: MSBuild' ## Design -`IBuildEngine` will be extended to allow a task to indicate to MSBuild that it would like to consume more than one CPU core (`BlockingWaitForCore`). These will be advisory only—a task can still do as much work as it desires with as many threads and processes as it desires. +`IBuildEngine` will be extended to allow a task to indicate to MSBuild that it would like to consume more than one CPU core (`RequestCores`). These will be advisory only — a task can still do as much work as it desires with as many threads and processes as it desires. A cooperating task would limit its own parallelism to the number of CPU cores MSBuild can reserve for the requesting task. -All resources acquired by a task will be automatically returned when the task's `Execute()` method returns, and a task can optionally return a subset by calling `ReleaseCores`. +`RequestCores(int requestedCores)` will always return a positive value, possibly less than the parameter if that many cores are not available. If no cores are available at the moment, the call blocks until at least one becomes available. The first `RequestCores` call made by a task is guaranteed to be non-blocking, though, as at minimum it will return the "implicit" core allocated to the task itself. This leads to two conceptual ways of adopting the API. Either the task calls `RequestCores` once, passing the desired number of cores, and then limiting its parallelism to whatever the call returns. Or the task makes additional calls throughout its execution, perhaps as it discovers more work to do. In this second scenario the task must be OK with waiting for additional cores for a long time or even forever if the sum of allocated cores has exceeded the limit defined by the policy. -MSBuild will respect core reservations given to tasks for tasks that opt into resource management only. If a project/task is eligible for execution, MSBuild will not wait until a resource is freed before starting execution of the new task. As a result, the machine can be oversubscribed, but only by a finite amount: the resource pool's core count. +All resources acquired by a task will be automatically returned when the task's `Execute()` method returns, and a task can optionally return a subset by calling `ReleaseCores`. Additionally, all resources will be returned when the task calls `Reacquire` as this call is a signal to the scheduler that external tools have finished their work and the task can continue running. It does not matter when the resources where allocated - whether it was before or after calling `Yield` - they will all be released. Depending on the scheduling policy, freeing resources on `Reacquire` may prevent deadlocks. -Task `Yield()`ing has no effect on the resources held by a task. +The exact core reservation policy and its interaction with task execution scheduling is still TBD. The pool of resources explicitly allocated by tasks may be completely separate, i.e. MSBuild will not wait until a resource is freed before starting execution of new tasks. Or it may be partially or fully shared to prevent oversubscribing the machine. In general, `ReleaseCores` may cause a transition of a waiting task to a Ready state. And vice-versa, completing a task or calling `Yield` may unblock a pending `RequestCores` call issued by a task. -## Example +## Example 1 In a 16-process build of a solution with 30 projects, 16 worker nodes are launched and begin executing work. Most block on dependencies to projects `A`, `B`, `C`, `D`, and `E`, so they don't have tasks running holding resources. @@ -30,16 +30,12 @@ Task `Work` is called in project `A` with 25 inputs. It would like to run as man int allowedParallelism = BuildEngine8.RequestCores(Inputs.Count); // Inputs.Count == 25 ``` -and gets `16`--the number of cores available to the build overall. Other tasks that do not call `RequestCores` do not affect this value. +and gets up to `16`--the number of cores available to the build overall. -While `A` runs `Work`, projects `B` and `C` run another task `Work2` that also calls `RequestCores` with a high value. Since `Work` in `A` has reserved all cores, the calls in `B` and `C` block, waiting on `Work` to release cores (or return). +While `A` runs `Work`, projects `B` and `C` run another task `Work2` that also calls `RequestCores` with a high value. Since `Work` in `A` has reserved all cores, the calls in `B` and `C` may return only 1, indicating that the task should not be doing parallel work. Subsequent `RequestCores` may block, waiting on `Work` to release cores (or return). -When `Work` returns, MSBuild automatically returns all resources reserved by the task to the pool. At that time `Work2`'s calls to `RequestCores` unblock, and +When `Work` returns, MSBuild automatically returns all resources reserved by the task to the pool. At that time blocked `RequestCores` calls in `Work2` may unblock. ## Implementation -The initial implementation of the system will use a Win32 [named semaphore](https://docs.microsoft.com/windows/win32/sync/semaphore-objects) to track resource use. This was the implementation of `MultiToolTask` in the VC++ toolchain and is a performant implementation of a counter that can be shared across processes. - -There is no guarantee of fair resource allocation. If multiple tasks are blocked in `RequestCores`, one of them will be unblocked when cores are released, but the returned cores may be split evenly, unevenly, or even entirely given to one task. - -On platforms where named semaphores are not supported (.NET Core MSBuild running on macOS, Linux, or other UNIXes), `RequestCores` will always return `1`. That is the minimum return value when the manager is fully functional, and hopefully will not dramatically overburden the machine. We will consider implementing full support using a cross-process semaphore (or an addition to the existing MSBuild communication protocol, if it isn't prohibitively costly to do the packet exchange and processing) on these platforms in the future. +The `RequestCores` and `ReleaseCores` calls are marshaled back to the scheduler via newly introduced `INodePacket` implementations. The scheduler, having full view of the state of the system - i.e. number of build requests running, waiting, yielding, ..., number of cores explicitly allocated by individual tasks using the new API - is free to implement an arbitrary core allocation policy. In the initial implementation the policy will be controlled by a couple of environment variables to make it easy to test different settings. From 3a554b8ffbd3b94cf8f9267d72d0c51a2cd6fe58 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Wed, 24 Mar 2021 16:21:47 +0100 Subject: [PATCH 084/105] Comments, renames, and tweaks --- src/Build/BackEnd/BuildManager/BuildManager.cs | 10 ++++++++-- .../BuildRequestEngine/BuildRequestEngine.cs | 11 +---------- .../Components/RequestBuilder/RequestBuilder.cs | 2 +- src/Build/BackEnd/Shared/ResourceRequest.cs | 13 +++++++------ 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 8cafdf5521b..60e92d7c2b6 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2180,10 +2180,15 @@ private void HandleNewRequest(int node, BuildRequestBlocker blocker) PerformSchedulingActions(response); } + /// + /// Handles a resource request coming from a node. + /// private void HandleResourceRequest(int node, ResourceRequest request) { - if (request.IsAcquire) + if (request.IsResourceAcquire) { + // Resource request requires a response and may be blocking. Our continuation is effectively a callback + // to be called once at least one core becomes available. _scheduler.RequestCores(request.BlockedRequestId, request.NumCores, request.IsBlocking).ContinueWith((Task task) => { var response = new ResourceResponse(request.BlockedRequestId, task.Result); @@ -2192,9 +2197,10 @@ private void HandleResourceRequest(int node, ResourceRequest request) } else { + // Resource release is a one-way call, no response is expected. We release the cores as instructed + // and kick the scheduler because there may be work waiting for cores to become available. IEnumerable response = _scheduler.ReleaseCores(request.BlockedRequestId, request.NumCores); PerformSchedulingActions(response); - // No response needed. } } diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index 859010d5d1d..f8d55075357 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -687,15 +687,6 @@ private void RaiseNewConfigurationRequest(BuildRequestConfiguration config) OnNewConfigurationRequest?.Invoke(config); } - /// - /// Raises OnResourceRequest event. - /// - /// The resource request. - private void RaiseResourceRequest(ResourceRequest request) - { - OnResourceRequest?.Invoke(request); - } - #endregion /// @@ -1015,7 +1006,7 @@ private void Builder_OnBlockedRequest(BuildRequestEntry issuingEntry, int blocki /// private void Builder_OnResourceRequest(ResourceRequest request) { - RaiseResourceRequest(request); + OnResourceRequest?.Invoke(request); } #endregion diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 3ac8c38a1c0..e9aadcd0adc 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -741,7 +741,7 @@ private void VerifyEntryInActiveState() private void VerifyEntryInActiveOrWaitingState() { ErrorUtilities.VerifyThrow(_requestEntry.State == BuildRequestEntryState.Active || _requestEntry.State == BuildRequestEntryState.Waiting, - "Entry is not in the Active state, it is in the {0} state.", _requestEntry.State); + "Entry is not in the Active or Waiting state, it is in the {0} state.", _requestEntry.State); } /// diff --git a/src/Build/BackEnd/Shared/ResourceRequest.cs b/src/Build/BackEnd/Shared/ResourceRequest.cs index 01abe649217..b3c3bd1f656 100644 --- a/src/Build/BackEnd/Shared/ResourceRequest.cs +++ b/src/Build/BackEnd/Shared/ResourceRequest.cs @@ -6,6 +6,7 @@ namespace Microsoft.Build.BackEnd { /// + /// This packet is sent by a node to request or release resources from/to the scheduler. /// internal class ResourceRequest : INodePacket { @@ -14,7 +15,7 @@ internal class ResourceRequest : INodePacket /// private int _blockedGlobalRequestId; - private bool _isAcquire; + private bool _isResourceAcquire; private bool _isBlocking; @@ -33,7 +34,7 @@ internal ResourceRequest(ITranslator translator) /// internal ResourceRequest(int blockedGlobalRequestId, int numCores, bool isBlocking) { - _isAcquire = true; + _isResourceAcquire = true; _isBlocking = isBlocking; _blockedGlobalRequestId = blockedGlobalRequestId; _numCores = numCores; @@ -44,7 +45,7 @@ internal ResourceRequest(int blockedGlobalRequestId, int numCores, bool isBlocki /// internal ResourceRequest(int blockedGlobalRequestId, int numCores) { - _isAcquire = false; + _isResourceAcquire = false; _blockedGlobalRequestId = blockedGlobalRequestId; _numCores = numCores; } @@ -73,12 +74,12 @@ public int BlockedRequestId /// /// - public bool IsAcquire + public bool IsResourceAcquire { [DebuggerStepThrough] get { - return _isAcquire; + return _isResourceAcquire; } } @@ -112,7 +113,7 @@ public int NumCores public void Translate(ITranslator translator) { translator.Translate(ref _blockedGlobalRequestId); - translator.Translate(ref _isAcquire); + translator.Translate(ref _isResourceAcquire); translator.Translate(ref _isBlocking); translator.Translate(ref _numCores); } From 63d782e7fdbfceb8c8416c9a30cfde373006fc76 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Wed, 24 Mar 2021 16:29:09 +0100 Subject: [PATCH 085/105] Remove the now unused ResourceManagerService --- src/Build.UnitTests/BackEnd/MockHost.cs | 7 --- .../BuildComponentFactoryCollection.cs | 3 - .../BackEnd/Components/IBuildComponentHost.cs | 5 -- .../Components/RequestBuilder/TaskHost.cs | 1 - .../ResourceManager/ResourceManagerService.cs | 55 ------------------- .../BackEnd/Components/Scheduler/Scheduler.cs | 3 - src/Build/Microsoft.Build.csproj | 1 - 7 files changed, 75 deletions(-) delete mode 100644 src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs diff --git a/src/Build.UnitTests/BackEnd/MockHost.cs b/src/Build.UnitTests/BackEnd/MockHost.cs index 48e0c18d755..1d787f19f91 100644 --- a/src/Build.UnitTests/BackEnd/MockHost.cs +++ b/src/Build.UnitTests/BackEnd/MockHost.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Build.BackEnd; -using Microsoft.Build.BackEnd.Components.ResourceManager; using Microsoft.Build.BackEnd.Logging; using System; using Microsoft.Build.BackEnd.SdkResolution; @@ -60,8 +59,6 @@ internal class MockHost : MockLoggingService, IBuildComponentHost, IBuildCompone private ISdkResolverService _sdkResolverService; - private readonly ResourceManagerService _taskResourceManager; - #region SystemParameterFields #endregion; @@ -105,9 +102,6 @@ public MockHost(BuildParameters buildParameters) _sdkResolverService = new MockSdkResolverService(); ((IBuildComponent)_sdkResolverService).InitializeComponent(this); - - _taskResourceManager = new ResourceManagerService(); - ((IBuildComponent)_taskResourceManager).InitializeComponent(this); } /// @@ -176,7 +170,6 @@ public IBuildComponent GetComponent(BuildComponentType type) BuildComponentType.ResultsCache => (IBuildComponent)_resultsCache, BuildComponentType.RequestBuilder => (IBuildComponent)_requestBuilder, BuildComponentType.SdkResolverService => (IBuildComponent)_sdkResolverService, - BuildComponentType.TaskResourceManager => (IBuildComponent)_taskResourceManager, _ => throw new ArgumentException("Unexpected type " + type), }; } diff --git a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs index 6c85709a550..05ea23f5425 100644 --- a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs +++ b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Microsoft.Build.BackEnd.Components.Caching; -using Microsoft.Build.BackEnd.Components.ResourceManager; using Microsoft.Build.BackEnd.SdkResolution; using Microsoft.Build.Shared; @@ -79,8 +78,6 @@ public void RegisterDefaultFactories() // SDK resolution _componentEntriesByType[BuildComponentType.SdkResolverService] = new BuildComponentEntry(BuildComponentType.SdkResolverService, MainNodeSdkResolverService.CreateComponent, CreationPattern.Singleton); - - _componentEntriesByType[BuildComponentType.TaskResourceManager] = new BuildComponentEntry(BuildComponentType.TaskResourceManager, ResourceManagerService.CreateComponent, CreationPattern.Singleton); } /// diff --git a/src/Build/BackEnd/Components/IBuildComponentHost.cs b/src/Build/BackEnd/Components/IBuildComponentHost.cs index da27a3049dc..4e57c5f2ae1 100644 --- a/src/Build/BackEnd/Components/IBuildComponentHost.cs +++ b/src/Build/BackEnd/Components/IBuildComponentHost.cs @@ -128,11 +128,6 @@ internal enum BuildComponentType /// The SDK resolution service. /// SdkResolverService, - - /// - /// Resource manager for tasks to use via . - /// - TaskResourceManager, } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 8f0d256aa6b..e0b0f699686 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -20,7 +20,6 @@ using TaskLoggingContext = Microsoft.Build.BackEnd.Logging.TaskLoggingContext; using System.Threading.Tasks; using Microsoft.Build.BackEnd.Components.Caching; -using Microsoft.Build.BackEnd.Components.ResourceManager; using System.Reflection; using Microsoft.Build.Eventing; diff --git a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs b/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs deleted file mode 100644 index 10934d2fbbc..00000000000 --- a/src/Build/BackEnd/Components/ResourceManager/ResourceManagerService.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Shared; -using Microsoft.Build.Utilities; - -using System.Threading; - -#nullable enable - -namespace Microsoft.Build.BackEnd.Components.ResourceManager -{ - class ResourceManagerService : IBuildComponent - { - //ILoggingService? _loggingService; - - public int TotalNumberHeld = -1; - public int Count = 0; - - internal static IBuildComponent CreateComponent(BuildComponentType type) - { - ErrorUtilities.VerifyThrow(type == BuildComponentType.TaskResourceManager, "Cannot create components of type {0}", type); - - return new ResourceManagerService(); - } - - public void InitializeComponent(IBuildComponentHost host) - { - - } - - public void ShutdownComponent() - { - //_loggingService = null; - - TotalNumberHeld = -2; - } - - public int RequestCores(int requestedCores, TaskLoggingContext _taskLoggingContext) - { - return 0; - - // _loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerRequestedCores", requestedCores, i, TotalNumberHeld); - } - - public void ReleaseCores(int coresToRelease, TaskLoggingContext _taskLoggingContext) - { - ErrorUtilities.VerifyThrow(coresToRelease > 0, "Tried to release {0} cores", coresToRelease); - return; - - //_loggingService?.LogComment(_taskLoggingContext.BuildEventContext, Framework.MessageImportance.Low, "ResourceManagerReleasedCores", coresToRelease, TotalNumberHeld); - } - } -} diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index c5a60da817b..3f085ed3335 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -10,7 +10,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.Build.BackEnd.Components.ResourceManager; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -122,7 +121,6 @@ internal class Scheduler : IScheduler /// The configuration cache. /// private IConfigCache _configCache; - private ResourceManagerService _resourceManager; /// /// The results cache. @@ -612,7 +610,6 @@ public void InitializeComponent(IBuildComponentHost host) _componentHost = host; _resultsCache = (IResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache); _configCache = (IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache); - _resourceManager = (ResourceManagerService)_componentHost.GetComponent(BuildComponentType.TaskResourceManager); } /// diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 31797c6619e..2dff53cc972 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -156,7 +156,6 @@ - From 874ecf3eed996dba845b554aeb99ce7f6a789a69 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Thu, 25 Mar 2021 13:27:17 +0100 Subject: [PATCH 086/105] Comments, renames, and tweaks --- .../BackEnd/BuildManager/BuildManager.cs | 6 +- .../BuildRequestEngine/BuildRequestEngine.cs | 2 +- .../RequestBuilder/IRequestBuilderCallback.cs | 2 +- .../RequestBuilder/RequestBuilder.cs | 5 +- .../Components/RequestBuilder/TaskHost.cs | 127 ++++++++++-------- .../Scheduler/SchedulableRequest.cs | 10 +- .../BackEnd/Components/Scheduler/Scheduler.cs | 8 +- .../Components/Scheduler/SchedulingData.cs | 10 +- src/Build/BackEnd/Node/InProcNode.cs | 3 + src/Build/BackEnd/Shared/ResourceRequest.cs | 72 ++++------ src/Build/BackEnd/Shared/ResourceResponse.cs | 42 ++---- 11 files changed, 134 insertions(+), 153 deletions(-) diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 60e92d7c2b6..30deca7f025 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -2189,9 +2189,9 @@ private void HandleResourceRequest(int node, ResourceRequest request) { // Resource request requires a response and may be blocking. Our continuation is effectively a callback // to be called once at least one core becomes available. - _scheduler.RequestCores(request.BlockedRequestId, request.NumCores, request.IsBlocking).ContinueWith((Task task) => + _scheduler.RequestCores(request.GlobalRequestId, request.NumCores, request.IsBlocking).ContinueWith((Task task) => { - var response = new ResourceResponse(request.BlockedRequestId, task.Result); + var response = new ResourceResponse(request.GlobalRequestId, task.Result); _nodeManager.SendData(node, response); }, TaskContinuationOptions.ExecuteSynchronously); } @@ -2199,7 +2199,7 @@ private void HandleResourceRequest(int node, ResourceRequest request) { // Resource release is a one-way call, no response is expected. We release the cores as instructed // and kick the scheduler because there may be work waiting for cores to become available. - IEnumerable response = _scheduler.ReleaseCores(request.BlockedRequestId, request.NumCores); + IEnumerable response = _scheduler.ReleaseCores(request.GlobalRequestId, request.NumCores); PerformSchedulingActions(response); } } diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index f8d55075357..1038643d11f 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -468,7 +468,7 @@ public void GrantResources(ResourceResponse response) QueueAction( () => { - BuildRequestEntry entry = _requestsByGlobalRequestId[response.BlockedRequestId]; + BuildRequestEntry entry = _requestsByGlobalRequestId[response.GlobalRequestId]; entry.Builder.ContinueRequestWithResources(response); }, isLastTask: false); diff --git a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs index 323f046e040..d3629adec11 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs @@ -62,7 +62,7 @@ internal interface IRequestBuilderCallback /// Number of logical cores being requested. /// True to make the request block and wait for at least one core. /// Number of logical cores actually granted. If is false, the call can return - /// zero. Otherwise the return value is always positive. + /// zero. Otherwise the return value is positive. int RequestCores(int requestedCores, bool waitForCores); /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index e9aadcd0adc..950091de93b 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -497,10 +497,7 @@ public int RequestCores(int requestedCores, bool waitForCores) RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, requestedCores, waitForCores)); WaitHandle[] handles = new WaitHandle[] { _terminateEvent, _continueWithResourcesEvent }; - - int handle = WaitHandle.WaitAny(handles); - - if (handle == 0) + if (WaitHandle.WaitAny(handles) == 0) { // We've been aborted throw new BuildAbortedException(); diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index e0b0f699686..8ceb6a72ece 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -680,17 +680,67 @@ public void LogTelemetry(string eventName, IDictionary propertie #region IBuildEngine8 Members - int runningTotal = 0; - bool implicitCoreUsed = false; + private ICollection _warningsAsErrors; + + /// + /// Additional cores granted to the task by the scheduler. Does not include the one implicit core automatically granted to all tasks. + /// + private int _additionalAcquiredCores = 0; + + /// + /// True if the one implicit core has been allocated by , false otherwise. + /// + private bool _isImplicitCoreUsed = false; + + /// + /// Contains all warnings that should be logged as errors. + /// Non-null empty set when all warnings should be treated as errors. + /// + private ICollection WarningsAsErrors + { + get + { + // Test compatibility + if(_taskLoggingContext == null) + { + return null; + } + + return _warningsAsErrors ??= _taskLoggingContext.GetWarningsAsErrors(); + } + } + + /// + /// Determines if the given warning should be treated as an error. + /// + /// + /// True if WarningsAsErrors is an empty set or contains the given warning code. + public bool ShouldTreatWarningAsError(string warningCode) + { + if (WarningsAsErrors == null) + { + return false; + } + // An empty set means all warnings are errors. + return WarningsAsErrors.Count == 0 || WarningsAsErrors.Contains(warningCode); + } + + /// + /// Allocates shared CPU resources. Called by a task when it's about to do potentially multi-threaded/multi-process work. + /// + /// The number of cores the task wants to use. + /// The number of cores the task is allowed to use given the current state of the build. This number is always between + /// 1 and . If the task has allocated its one implicit core, this call may block, waiting for + /// at least one core to become available. public int RequestCores(int requestedCores) { lock (_callbackMonitor) { - int coresAcquired = 0; - IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; - if (implicitCoreUsed) + + int coresAcquired = 0; + if (_isImplicitCoreUsed) { coresAcquired = builderCallback.RequestCores(requestedCores, waitForCores: true); } @@ -698,87 +748,56 @@ public int RequestCores(int requestedCores) { coresAcquired = builderCallback.RequestCores(requestedCores - 1, waitForCores: false); } - runningTotal += coresAcquired; + _additionalAcquiredCores += coresAcquired; - if (!implicitCoreUsed) + if (!_isImplicitCoreUsed) { - // If we got nothing back from the actual system, pad it with the one implicit core - // you get just for running--that way the first call never blocks and always returns >= 1 - implicitCoreUsed = true; + // Pad the result with the one implicit core. This ensures that first call never blocks and always returns >= 1. + _isImplicitCoreUsed = true; coresAcquired++; } Debug.Assert(coresAcquired >= 1); - return coresAcquired; } } + /// + /// Frees shared CPU resources. Called by a task when it's finished doing multi-threaded/multi-process work. + /// + /// The number of cores the task wants to return. This number must be between 0 and the number of cores + /// granted and not yet released. public void ReleaseCores(int coresToRelease) { lock (_callbackMonitor) { - if (implicitCoreUsed && coresToRelease > runningTotal) + if (_isImplicitCoreUsed && coresToRelease > _additionalAcquiredCores) { + // Release the implicit core last, i.e. only if we're asked to release everything. coresToRelease -= 1; - implicitCoreUsed = false; + _isImplicitCoreUsed = false; } if (coresToRelease >= 1) { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; builderCallback.ReleaseCores(coresToRelease); - runningTotal -= coresToRelease; + _additionalAcquiredCores -= coresToRelease; } } } - internal void ReleaseAllCores() - { - ReleaseCores(runningTotal + (implicitCoreUsed ? 1 : 0)); - - runningTotal = 0; - implicitCoreUsed = false; - } - - #endregion - - #region IBuildEngine8 Members - private ICollection _warningsAsErrors; - /// - /// Contains all warnings that should be logged as errors. - /// Non-null empty set when all warnings should be treated as errors. + /// Frees all CPU resources granted so far. /// - private ICollection WarningsAsErrors + internal void ReleaseAllCores() { - get - { - // Test compatibility - if(_taskLoggingContext == null) - { - return null; - } + ReleaseCores(_additionalAcquiredCores + (_isImplicitCoreUsed ? 1 : 0)); - return _warningsAsErrors ??= _taskLoggingContext.GetWarningsAsErrors(); - } + _additionalAcquiredCores = 0; + _isImplicitCoreUsed = false; } - /// - /// Determines if the given warning should be treated as an error. - /// - /// - /// True if WarningsAsErrors is an empty set or contains the given warning code. - public bool ShouldTreatWarningAsError(string warningCode) - { - if (WarningsAsErrors == null) - { - return false; - } - - // An empty set means all warnings are errors. - return WarningsAsErrors.Count == 0 || WarningsAsErrors.Contains(warningCode); - } #endregion /// diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs b/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs index 2382a4bcc3b..b8541e89b1c 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs @@ -120,9 +120,9 @@ internal class SchedulableRequest private Dictionary _timeRecords; /// - /// Number of cores requested as part of running the build request. + /// Number of cores granted as part of running the build request. /// - private int _requestedCores; + private int _grantedCores; /// /// Constructor. @@ -295,10 +295,10 @@ public DateTime EndTime } } - public int RequestedCores => _requestedCores; + public int GrantedCores => _grantedCores; - public void AddRequestedCores(int cores) => _requestedCores += cores; - public int RemoveRequestedCores(int cores) => _requestedCores = Math.Max(0, _requestedCores - cores); + public void AddGrantedCores(int cores) => _grantedCores += cores; + public void RemoveGrantedCores(int cores) => _grantedCores = Math.Max(0, _grantedCores - cores); /// /// Gets the amount of time we spent in the specified state. diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 3f085ed3335..360cdcc5169 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -564,7 +564,7 @@ public Task RequestCores(int requestId, int requestedCores, bool waitForCor if (grantedCores > 0) { SchedulableRequest request = _schedulingData.GetScheduledRequest(requestId); - request.AddRequestedCores(grantedCores); + request.AddGrantedCores(grantedCores); } return grantedCores; }; @@ -589,7 +589,7 @@ public Task RequestCores(int requestId, int requestedCores, bool waitForCor public List ReleaseCores(int requestId, int coresToRelease) { SchedulableRequest request = _schedulingData.GetScheduledRequest(requestId); - request.RemoveRequestedCores(coresToRelease); + request.RemoveGrantedCores(coresToRelease); // Releasing cores means that we may be able to schedule more work. List responses = new List(); @@ -1364,13 +1364,13 @@ private int GetAvailableCoresForScheduling() _ => _componentHost.BuildParameters.MaxNodeCount + 2 + _nodeLimitOffset, }; - return Math.Max(0, limit - (_schedulingData.ExecutingRequestsCount + _schedulingData.ExplicitlyRequestedCores + _schedulingData.YieldingRequestsCount)); + return Math.Max(0, limit - (_schedulingData.ExecutingRequestsCount + _schedulingData.ExplicitlyGrantedCores + _schedulingData.YieldingRequestsCount)); } private int GetAvailableCoresForExplicitRequests() { int implicitlyAllocatedCores = ((_schedulingData.ExecutingRequestsCount - 1) * _nodeCoreAllocationWeight) / 100; - int explicitlyAllocatedCores = _schedulingData.ExplicitlyRequestedCores; + int explicitlyAllocatedCores = _schedulingData.ExplicitlyGrantedCores; return Math.Max(0, _coreLimit - (implicitlyAllocatedCores + explicitlyAllocatedCores)); } diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs index 99512373e62..f4592ac5b32 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs @@ -154,11 +154,11 @@ public int ReadyRequestsCount } /// - /// Gets the total number of cores requested by executing and yielding build requests. + /// Gets the total number of cores granted to executing and yielding build requests. /// - public int ExplicitlyRequestedCores + public int ExplicitlyGrantedCores { - get { return _executingRequests.Sum(kvp => kvp.Value.RequestedCores) + _yieldingRequests.Sum(kvp => kvp.Value.RequestedCores); } + get { return _executingRequests.Sum(kvp => kvp.Value.GrantedCores) + _yieldingRequests.Sum(kvp => kvp.Value.GrantedCores); } } /// @@ -486,7 +486,7 @@ public SchedulableRequest GetReadyRequest(int globalRequestId) } /// - /// Retrieves a request which has been assigned to a node and is in the executing, yielding, blocked, ready states. + /// Retrieves a request which has been assigned to a node and is in the executing, yielding, blocked, or ready states. /// public SchedulableRequest GetScheduledRequest(int globalRequestId) { @@ -508,7 +508,7 @@ public bool IsNodeWorking(int nodeId) foreach (KeyValuePair kvp in _yieldingRequests) { - if (kvp.Value.AssignedNode == nodeId && kvp.Value.RequestedCores > 0) + if (kvp.Value.AssignedNode == nodeId && kvp.Value.GrantedCores > 0) { // This node does not have an executing task on it. However, it does have a yielding task // that has explicitly asked for cores which makes it "working". diff --git a/src/Build/BackEnd/Node/InProcNode.cs b/src/Build/BackEnd/Node/InProcNode.cs index bfcecffd271..81b4ab63279 100644 --- a/src/Build/BackEnd/Node/InProcNode.cs +++ b/src/Build/BackEnd/Node/InProcNode.cs @@ -524,6 +524,9 @@ private void HandleNodeBuildComplete(NodeBuildComplete buildComplete) _shutdownEvent.Set(); } + /// + /// Handles the ResourceResponse packet. + /// private void HandleResourceResponse(ResourceResponse response) { _buildRequestEngine.GrantResources(response); diff --git a/src/Build/BackEnd/Shared/ResourceRequest.cs b/src/Build/BackEnd/Shared/ResourceRequest.cs index b3c3bd1f656..da5b5dc7b8a 100644 --- a/src/Build/BackEnd/Shared/ResourceRequest.cs +++ b/src/Build/BackEnd/Shared/ResourceRequest.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Diagnostics; - namespace Microsoft.Build.BackEnd { /// @@ -13,12 +11,22 @@ internal class ResourceRequest : INodePacket /// /// The global request id of the request which is asking for resources. /// - private int _blockedGlobalRequestId; + private int _globalRequestId; + /// + /// True if this is a request to acquire resources, false if this is a request to release resources. + /// private bool _isResourceAcquire; + /// + /// True if the request should be blocking until the resources become available. False if the request should + /// be responded to immediately even if the desired resources are not available. + /// private bool _isBlocking; + /// + /// Number of CPU cores being requested or released. + /// private int _numCores; /// @@ -32,78 +40,48 @@ internal ResourceRequest(ITranslator translator) /// /// Constructor for acquiring. /// - internal ResourceRequest(int blockedGlobalRequestId, int numCores, bool isBlocking) + internal ResourceRequest(int globalRequestId, int numCores, bool isBlocking) { _isResourceAcquire = true; _isBlocking = isBlocking; - _blockedGlobalRequestId = blockedGlobalRequestId; + _globalRequestId = globalRequestId; _numCores = numCores; } /// /// Constructor for releasing. /// - internal ResourceRequest(int blockedGlobalRequestId, int numCores) + internal ResourceRequest(int globalRequestId, int numCores) { _isResourceAcquire = false; - _blockedGlobalRequestId = blockedGlobalRequestId; + _globalRequestId = globalRequestId; _numCores = numCores; } /// /// Returns the type of packet. /// - public NodePacketType Type - { - [DebuggerStepThrough] - get - { return NodePacketType.ResourceRequest; } - } + public NodePacketType Type => NodePacketType.ResourceRequest; /// - /// Accessor for the blocked request id. + /// Accessor for the global request id. /// - public int BlockedRequestId - { - [DebuggerStepThrough] - get - { - return _blockedGlobalRequestId; - } - } + public int GlobalRequestId => _globalRequestId; /// + /// Accessor for _isResourceAcquire. /// - public bool IsResourceAcquire - { - [DebuggerStepThrough] - get - { - return _isResourceAcquire; - } - } + public bool IsResourceAcquire => _isResourceAcquire; /// + /// Accessor fro _isBlocking. /// - public bool IsBlocking - { - [DebuggerStepThrough] - get - { - return _isBlocking; - } - } + public bool IsBlocking => _isBlocking; /// + /// Accessor for _numCores. /// - public int NumCores - { - [DebuggerStepThrough] - get - { - return _numCores; - } - } + public int NumCores => _numCores; #region INodePacketTranslatable Members @@ -112,7 +90,7 @@ public int NumCores /// public void Translate(ITranslator translator) { - translator.Translate(ref _blockedGlobalRequestId); + translator.Translate(ref _globalRequestId); translator.Translate(ref _isResourceAcquire); translator.Translate(ref _isBlocking); translator.Translate(ref _numCores); diff --git a/src/Build/BackEnd/Shared/ResourceResponse.cs b/src/Build/BackEnd/Shared/ResourceResponse.cs index fddc805598b..e603307d7c3 100644 --- a/src/Build/BackEnd/Shared/ResourceResponse.cs +++ b/src/Build/BackEnd/Shared/ResourceResponse.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Diagnostics; - namespace Microsoft.Build.BackEnd { /// @@ -12,8 +10,11 @@ internal class ResourceResponse : INodePacket /// /// The global request id of the request which is being responded to. /// - private int _blockedGlobalRequestId; + private int _globalRequestId; + /// + /// Number of CPU cores being granted. + /// private int _numCores; /// @@ -25,45 +26,28 @@ internal ResourceResponse(ITranslator translator) } /// + /// Constructor for granting cores. /// - internal ResourceResponse(int blockedGlobalRequestId, int numCores) + internal ResourceResponse(int globalRequestId, int numCores) { - _blockedGlobalRequestId = blockedGlobalRequestId; + _globalRequestId = globalRequestId; _numCores = numCores; } /// /// Returns the type of packet. /// - public NodePacketType Type - { - [DebuggerStepThrough] - get - { return NodePacketType.ResourceResponse; } - } + public NodePacketType Type => NodePacketType.ResourceResponse; /// - /// Accessor for the blocked request id. + /// Accessor for the global request id. /// - public int BlockedRequestId - { - [DebuggerStepThrough] - get - { - return _blockedGlobalRequestId; - } - } + public int GlobalRequestId => _globalRequestId; /// + /// Accessor for _numCores. /// - public int NumCores - { - [DebuggerStepThrough] - get - { - return _numCores; - } - } + public int NumCores => _numCores; #region INodePacketTranslatable Members @@ -72,7 +56,7 @@ public int NumCores /// public void Translate(ITranslator translator) { - translator.Translate(ref _blockedGlobalRequestId); + translator.Translate(ref _globalRequestId); translator.Translate(ref _numCores); } From 8ed06ad47ecf6a2e5b34e6c29aa9459735b0a571 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Thu, 25 Mar 2021 13:58:35 +0100 Subject: [PATCH 087/105] Comments, renames, and tweaks --- .../BackEnd/Components/Scheduler/Scheduler.cs | 22 +++++++++++-------- src/Shared/Traits.cs | 6 ----- src/Shared/UnitTests/MockEngine.cs | 2 -- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 360cdcc5169..4e8ceedea73 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -72,7 +72,7 @@ internal class Scheduler : IScheduler private int _nodeLimitOffset; /// - /// NativeMethodsShared.GetLogicalCoreCount() or MSBUILDCORELIMIT if set + /// The result of calling NativeMethodsShared.GetLogicalCoreCount() unless overriden with MSBUILDCORELIMIT. /// private int _coreLimit; @@ -106,7 +106,7 @@ internal class Scheduler : IScheduler private SchedulingData _schedulingData; /// - /// A queue of RequestCores request waiting for at least one core to become available. + /// A queue of RequestCores requests waiting for at least one core to become available. /// private Queue> _pendingRequestCoresCallbacks = new Queue>(); @@ -201,10 +201,6 @@ public Scheduler() { _coreLimit = NativeMethodsShared.GetLogicalCoreCount(); } - // Tasks are factoring in the "implicit core" so let's make the maximum return value from - // RequestCore exactly the number of cores. - _coreLimit = Math.Max(0, _coreLimit - 1); - if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDNODECOREALLOCATIONWEIGHT"), out _nodeCoreAllocationWeight) || _nodeCoreAllocationWeight <= 0 || _nodeCoreAllocationWeight > 100) @@ -1367,11 +1363,19 @@ private int GetAvailableCoresForScheduling() return Math.Max(0, limit - (_schedulingData.ExecutingRequestsCount + _schedulingData.ExplicitlyGrantedCores + _schedulingData.YieldingRequestsCount)); } + /// + /// Returns the maximum number of cores that can be returned from a RequestCores() call at the moment. + /// private int GetAvailableCoresForExplicitRequests() { - int implicitlyAllocatedCores = ((_schedulingData.ExecutingRequestsCount - 1) * _nodeCoreAllocationWeight) / 100; - int explicitlyAllocatedCores = _schedulingData.ExplicitlyGrantedCores; - return Math.Max(0, _coreLimit - (implicitlyAllocatedCores + explicitlyAllocatedCores)); + // At least one core is always implicitly granted to the node making the request. + // If _nodeCoreAllocationWeight is more than zero, it can increase this value by the specified fraction of executing nodes. + int implicitlyGrantedCores = Math.Max(1, ((_schedulingData.ExecutingRequestsCount - 1) * _nodeCoreAllocationWeight) / 100); + + // The number of explicitly granted cores is a sum of everything we've granted via RequestCores() so far across all nodes. + int explicitlyGrantedCores = _schedulingData.ExplicitlyGrantedCores; + + return Math.Max(0, _coreLimit - (implicitlyGrantedCores + explicitlyGrantedCores)); } /// diff --git a/src/Shared/Traits.cs b/src/Shared/Traits.cs index f23c02fa72f..7aa78f0bbeb 100644 --- a/src/Shared/Traits.cs +++ b/src/Shared/Traits.cs @@ -86,12 +86,6 @@ public Traits() /// public readonly int LogPropertyTracking = ParseIntFromEnvironmentVariableOrDefault("MsBuildLogPropertyTracking", 0); // Default to logging nothing via the property tracker. - /// - /// Allow tasks to collect more resources than the default. - /// - public readonly int ResourceManagerOversubscription = ParseIntFromEnvironmentVariableOrDefault("MSBUILDRESOURCEMANAGEROVERSUBSCRIPTION", 0); // Default to maxcpucount - - private static int ParseIntFromEnvironmentVariableOrDefault(string environmentVariable, int defaultValue) { return int.TryParse(Environment.GetEnvironmentVariable(environmentVariable), out int result) diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index e8a2af31f03..a9ee96a69ea 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -33,7 +33,6 @@ namespace Microsoft.Build.UnitTests **************************************************************************/ internal sealed class MockEngine : IBuildEngine8 { - private readonly string ResourceSemaphoreName = $"MSBuildTestResourceSemaphore{Guid.NewGuid().ToString()}"; private readonly object _lockObj = new object(); // Protects _log, _output private readonly ITestOutputHelper _output; private readonly StringBuilder _log = new StringBuilder(); @@ -504,6 +503,5 @@ public bool ShouldTreatWarningAsError(string warningCode) { return false; } - } } From 97e9f5d35f90057230ebfe005f109a33fa69b9ed Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Thu, 25 Mar 2021 13:58:45 +0100 Subject: [PATCH 088/105] Revert string changes --- src/Build/Resources/Strings.resx | 9 --------- src/Build/Resources/xlf/Strings.cs.xlf | 15 --------------- src/Build/Resources/xlf/Strings.de.xlf | 15 --------------- src/Build/Resources/xlf/Strings.en.xlf | 15 --------------- src/Build/Resources/xlf/Strings.es.xlf | 15 --------------- src/Build/Resources/xlf/Strings.fr.xlf | 15 --------------- src/Build/Resources/xlf/Strings.it.xlf | 15 --------------- src/Build/Resources/xlf/Strings.ja.xlf | 15 --------------- src/Build/Resources/xlf/Strings.ko.xlf | 15 --------------- src/Build/Resources/xlf/Strings.pl.xlf | 15 --------------- src/Build/Resources/xlf/Strings.pt-BR.xlf | 15 --------------- src/Build/Resources/xlf/Strings.ru.xlf | 15 --------------- src/Build/Resources/xlf/Strings.tr.xlf | 15 --------------- src/Build/Resources/xlf/Strings.zh-Hans.xlf | 15 --------------- src/Build/Resources/xlf/Strings.zh-Hant.xlf | 15 --------------- 15 files changed, 219 deletions(-) diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 260a7e6a83f..8a8a77610d4 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1879,15 +1879,6 @@ Utilization: {0} Average Utilization: {1:###.0} MSB4270: No project cache plugins found in assembly "{0}". Expected one. - - Task released {0} cores and now holds {1}. - - - Task requested {0} cores and recieved {1}. It now holds {2}. - - - MSB4270: Task attempted to release {0} cores but held only {1}. - Killing process with pid = {0}. diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index ad765ac1b5f..6f48daf8d36 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -252,21 +252,6 @@ Počáteční hodnota vlastnosti: $({0})={1} Zdroj: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: Projekt {0} přeskočil omezení izolace grafu v odkazovaném projektu {1}. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index c34c99cd22f..3e12dd793c1 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -252,21 +252,6 @@ Anfangswert der Eigenschaft: $({0})="{1}", Quelle: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: Das Projekt "{0}" hat Graphisolationseinschränkungen für das referenzierte Projekt "{1}" übersprungen. diff --git a/src/Build/Resources/xlf/Strings.en.xlf b/src/Build/Resources/xlf/Strings.en.xlf index 38f6b48dde0..557613e2413 100644 --- a/src/Build/Resources/xlf/Strings.en.xlf +++ b/src/Build/Resources/xlf/Strings.en.xlf @@ -252,21 +252,6 @@ Property initial value: $({0})="{1}" Source: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 5b5e58ebde3..d2a6f93df72 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -252,21 +252,6 @@ Valor inicial de la propiedad: $({0})="{1}" Origen: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: El proyecto "{0}" ha omitido las restricciones de aislamiento de gráficos en el proyecto "{1}" al que se hace referencia. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 9d39f197d55..86246b0867f 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -252,21 +252,6 @@ Valeur initiale de la propriété : $({0})="{1}" Source : {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: le projet "{0}" a ignoré les contraintes d'isolement de graphe dans le projet référencé "{1}" diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 94dae62a464..0dd8e7bce9d 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -252,21 +252,6 @@ Valore iniziale della proprietà: $({0})="{1}". Origine: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: il progetto "{0}" ha ignorato i vincoli di isolamento del grafico nel progetto di riferimento "{1}" diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index acb60c9061c..192beb71ced 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -252,21 +252,6 @@ プロパティの初期値: $({0})="{1}" ソース: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: プロジェクト "{0}" は、参照先のプロジェクト "{1}" で、グラフの分離制約をスキップしました diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 3454ee65d4d..601f8e49a00 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -252,21 +252,6 @@ 속성 초기 값: $({0})="{1}" 소스: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: 프로젝트 "{0}"에서 참조된 프로젝트 "{1}"의 그래프 격리 제약 조건을 건너뛰었습니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 5888f55f95b..777231c77a5 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -252,21 +252,6 @@ Wartość początkowa właściwości: $({0})=„{1}” Źródło: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: W przypadku projektu „{0}” pominięto ograniczenia izolacji grafu dla przywoływanego projektu „{1}” diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 855d9b03d5e..04e1a3d63eb 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -252,21 +252,6 @@ Valor inicial da propriedade: $({0})="{1}" Origem: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: o projeto "{0}" ignorou as restrições de isolamento do gráfico no projeto referenciado "{1}" diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index be6386d8f60..3da7d8b5bb3 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -252,21 +252,6 @@ Начальное значение свойства: $({0})="{1}" Источник: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: проект "{0}" пропустил ограничения изоляции графа в проекте "{1}", на который указывает ссылка. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 49c7a69b311..cf33e768a2f 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -252,21 +252,6 @@ Özellik başlangıç değeri: $({0})="{1}" Kaynak: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: "{0}" projesi, başvurulan "{1}" projesindeki graf yalıtımı kısıtlamalarını atladı diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 3d1763cc0fe..b257204a1d9 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -252,21 +252,6 @@ 属性初始值: $({0})=“{1}”,源: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: 项目“{0}”已跳过所引用的项目“{1}”上的图形隔离约束 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 3376c7c8c2e..9d6e4a89f4a 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -252,21 +252,6 @@ 屬性初始值: $({0})="{1}" 來源: {2} - - MSB4270: Task attempted to release {0} cores but held only {1}. - MSB4270: Task attempted to release {0} cores but held only {1}. - - - - Task released {0} cores and now holds {1}. - Task released {0} cores and now holds {1}. - - - - Task requested {0} cores and recieved {1}. It now holds {2}. - Task requested {0} cores and recieved {1}. It now holds {2}. - - MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}" MSB4260: 專案 "{0}" 已跳過參考專案 "{1}" 上的圖形隔離條件約束 From 531ee863136dd5d68b4538e41f8438c93b66bcdd Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Thu, 25 Mar 2021 15:55:01 +0100 Subject: [PATCH 089/105] Tweaks in Scheduler.cs --- .../BackEnd/Components/Scheduler/Scheduler.cs | 42 +++++++++---------- .../Components/Scheduler/SchedulingData.cs | 2 +- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 4e8ceedea73..71cd881368e 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -108,7 +108,7 @@ internal class Scheduler : IScheduler /// /// A queue of RequestCores requests waiting for at least one core to become available. /// - private Queue> _pendingRequestCoresCallbacks = new Queue>(); + private Queue> _pendingRequestCoresCallbacks; #endregion @@ -513,8 +513,8 @@ public void Reset() DumpRequests(); _schedulingPlan = null; _schedulingData = new SchedulingData(); - _pendingRequestCoresCallbacks = new Queue>(); _availableNodes = new Dictionary(8); + _pendingRequestCoresCallbacks = new Queue>(); _currentInProcNodeCount = 0; _currentOutOfProcNodeCount = 0; @@ -1346,23 +1346,6 @@ private void AssignUnscheduledRequestToNode(SchedulableRequest request, int node request.ResumeExecution(nodeId); } - private int GetAvailableCoresForScheduling() - { - if (_schedulingUnlimited) - { - return int.MaxValue; - } - - int limit = _componentHost.BuildParameters.MaxNodeCount switch - { - 1 => 1, - 2 => _componentHost.BuildParameters.MaxNodeCount + 1 + _nodeLimitOffset, - _ => _componentHost.BuildParameters.MaxNodeCount + 2 + _nodeLimitOffset, - }; - - return Math.Max(0, limit - (_schedulingData.ExecutingRequestsCount + _schedulingData.ExplicitlyGrantedCores + _schedulingData.YieldingRequestsCount)); - } - /// /// Returns the maximum number of cores that can be returned from a RequestCores() call at the moment. /// @@ -1390,10 +1373,23 @@ private bool AtSchedulingLimit() // We're at our limit of schedulable requests if: // (1) MaxNodeCount requests are currently executing - // (2) Fewer than MaxNodeCount requests are currently executing but the sum of executing - // and yielding requests exceeds the limit set out above. - return GetAvailableCoresForScheduling() == 0 || - _schedulingData.ExecutingRequestsCount >= _componentHost.BuildParameters.MaxNodeCount; + if (_schedulingData.ExecutingRequestsCount >= _componentHost.BuildParameters.MaxNodeCount) + { + return true; + } + + // (2) Fewer than MaxNodeCount requests are currently executing but the sum of executing request, + // yielding requests, and explicitly granted cores exceeds the limit set out below. + int limit = _componentHost.BuildParameters.MaxNodeCount switch + { + 1 => 1, + 2 => _componentHost.BuildParameters.MaxNodeCount + 1 + _nodeLimitOffset, + _ => _componentHost.BuildParameters.MaxNodeCount + 2 + _nodeLimitOffset, + }; + + return _schedulingData.ExecutingRequestsCount + + _schedulingData.YieldingRequestsCount + + _schedulingData.ExplicitlyGrantedCores >= limit; } /// diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs index f4592ac5b32..d4f64dde4f7 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Build.Shared; using Microsoft.Build.Collections; -using System.Linq; namespace Microsoft.Build.BackEnd { From 27865a86c1221849d1b4b230c43f9523faf97e0c Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Thu, 25 Mar 2021 16:43:51 +0100 Subject: [PATCH 090/105] Refactor SchedulingData & SchedulableRequest, don't consider nodes with Yielded tasks busy --- .../Scheduler/SchedulableRequest.cs | 13 ++--- .../BackEnd/Components/Scheduler/Scheduler.cs | 6 +-- .../Components/Scheduler/SchedulingData.cs | 53 ++++++++++++++----- 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs b/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs index b8541e89b1c..0dbbd72b7f6 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulableRequest.cs @@ -119,11 +119,6 @@ internal class SchedulableRequest /// private Dictionary _timeRecords; - /// - /// Number of cores granted as part of running the build request. - /// - private int _grantedCores; - /// /// Constructor. /// @@ -295,10 +290,10 @@ public DateTime EndTime } } - public int GrantedCores => _grantedCores; - - public void AddGrantedCores(int cores) => _grantedCores += cores; - public void RemoveGrantedCores(int cores) => _grantedCores = Math.Max(0, _grantedCores - cores); + /// + /// Number of cores granted as part of running the build request. + /// + public int GrantedCores { get; set; } /// /// Gets the amount of time we spent in the specified state. diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 71cd881368e..8277256ac0f 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -559,8 +559,7 @@ public Task RequestCores(int requestId, int requestedCores, bool waitForCor int grantedCores = Math.Min(requestedCores, availableCores); if (grantedCores > 0) { - SchedulableRequest request = _schedulingData.GetScheduledRequest(requestId); - request.AddGrantedCores(grantedCores); + _schedulingData.GrantCoresToRequest(requestId, grantedCores); } return grantedCores; }; @@ -584,8 +583,7 @@ public Task RequestCores(int requestId, int requestedCores, bool waitForCor /// public List ReleaseCores(int requestId, int coresToRelease) { - SchedulableRequest request = _schedulingData.GetScheduledRequest(requestId); - request.RemoveGrantedCores(coresToRelease); + _schedulingData.RemoveCoresFromRequest(requestId, coresToRelease); // Releasing cores means that we may be able to schedule more work. List responses = new List(); diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs index d4f64dde4f7..84e42f3f073 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulingData.cs @@ -87,6 +87,15 @@ internal class SchedulingData #endregion + #region Resource management + + /// + /// The sum of number of cores explicitly granted to all build requests. + /// + private int _grantedCores; + + #endregion + #region Diagnostic Information /// @@ -158,7 +167,7 @@ public int ReadyRequestsCount /// public int ExplicitlyGrantedCores { - get { return _executingRequests.Sum(kvp => kvp.Value.GrantedCores) + _yieldingRequests.Sum(kvp => kvp.Value.GrantedCores); } + get { return _grantedCores; } } /// @@ -501,21 +510,12 @@ public SchedulableRequest GetScheduledRequest(int globalRequestId) public bool IsNodeWorking(int nodeId) { SchedulableRequest request; - if (_executingRequestByNode.TryGetValue(nodeId, out request) && request != null) + if (!_executingRequestByNode.TryGetValue(nodeId, out request)) { - return true; + return false; } - foreach (KeyValuePair kvp in _yieldingRequests) - { - if (kvp.Value.AssignedNode == nodeId && kvp.Value.GrantedCores > 0) - { - // This node does not have an executing task on it. However, it does have a yielding task - // that has explicitly asked for cores which makes it "working". - return true; - } - } - return false; + return request != null; } /// @@ -651,6 +651,33 @@ public bool CanScheduleRequestToNode(SchedulableRequest request, int nodeId) return requiredNodeId == Scheduler.InvalidNodeId || requiredNodeId == nodeId; } + /// + /// Explicitly grants CPU cores to a request. + /// + public void GrantCoresToRequest(int globalRequestId, int coresToGrant) + { + // Update per-request state. + SchedulableRequest request = GetScheduledRequest(globalRequestId); + request.GrantedCores += coresToGrant; + + // Update global state. + _grantedCores += coresToGrant; + } + + /// + /// Explicitly removes previously granted CPU cores from a request. + /// + public void RemoveCoresFromRequest(int globalRequestId, int coresToRemove) + { + // Update per-request state. + SchedulableRequest request = GetScheduledRequest(globalRequestId); + coresToRemove = Math.Min(request.GrantedCores, coresToRemove); + request.GrantedCores -= coresToRemove; + + // Update global state. + _grantedCores -= coresToRemove; + } + /// /// Unassigns the node associated with a particular configuration. /// From 8016cf99b94c132c2a9801b2248ccd0f48c2d1a3 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 26 Mar 2021 10:11:18 +0100 Subject: [PATCH 091/105] Don't wait for ResourceResponse under a lock --- .../BackEnd/TargetBuilder_Tests.cs | 2 +- .../BackEnd/TargetEntry_Tests.cs | 2 +- .../BackEnd/TaskBuilder_Tests.cs | 2 +- src/Build.UnitTests/BackEnd/TaskHost_Tests.cs | 2 +- .../RequestBuilder/IRequestBuilderCallback.cs | 3 +- .../RequestBuilder/RequestBuilder.cs | 32 +++++++++++++++++-- .../RequestBuilder/TargetBuilder.cs | 4 +-- .../Components/RequestBuilder/TaskHost.cs | 18 +++++++---- 8 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs index 2d9b1f3a9e7..fe416e2e901 100644 --- a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs @@ -1420,7 +1420,7 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Empty impl /// - int IRequestBuilderCallback.RequestCores(int requestedCores, bool waitForCores) + int IRequestBuilderCallback.RequestCores(object monitorLockObject, int requestedCores, bool waitForCores) { return 0; } diff --git a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs index 6d73dcd4bed..faa5e75e688 100644 --- a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs @@ -981,7 +981,7 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Empty impl /// - int IRequestBuilderCallback.RequestCores(int requestedCores, bool waitForCores) + int IRequestBuilderCallback.RequestCores(object monitorLockObject, int requestedCores, bool waitForCores) { return 0; } diff --git a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs index 2bf9f56669f..6e1556b5712 100644 --- a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs @@ -758,7 +758,7 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Empty impl /// - int IRequestBuilderCallback.RequestCores(int requestedCores, bool waitForCores) + int IRequestBuilderCallback.RequestCores(object monitorLockObject, int requestedCores, bool waitForCores) { return 0; } diff --git a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs index a4c790a2209..beb972e73fe 100644 --- a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs @@ -1302,7 +1302,7 @@ public void ExitMSBuildCallbackState() /// /// Mock /// - public int RequestCores(int requestedCores, bool waitForCores) + public int RequestCores(object monitorLockObject, int requestedCores, bool waitForCores) { return 0; } diff --git a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs index d3629adec11..386db137259 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IRequestBuilderCallback.cs @@ -59,11 +59,12 @@ internal interface IRequestBuilderCallback /// /// Requests CPU resources from the scheduler. /// + /// The object used by the caller for synchronization. The lock on this object must be taken when calling this method. /// Number of logical cores being requested. /// True to make the request block and wait for at least one core. /// Number of logical cores actually granted. If is false, the call can return /// zero. Otherwise the return value is positive. - int RequestCores(int requestedCores, bool waitForCores); + int RequestCores(object monitorLockObject, int requestedCores, bool waitForCores); /// /// Returns CPU resources to the scheduler. diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 950091de93b..186c62461f5 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -491,20 +491,46 @@ public void ExitMSBuildCallbackState() /// /// Requests CPU resources from the scheduler. /// - public int RequestCores(int requestedCores, bool waitForCores) + public int RequestCores(object monitorLockObject, int requestedCores, bool waitForCores) { VerifyIsNotZombie(); RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, requestedCores, waitForCores)); WaitHandle[] handles = new WaitHandle[] { _terminateEvent, _continueWithResourcesEvent }; - if (WaitHandle.WaitAny(handles) == 0) + + Task.Run(() => + { + int waitResult = WaitHandle.WaitAny(handles); + lock (monitorLockObject) + { + if (waitResult == 0) + { + // Notify all threads of a build abort. + Monitor.PulseAll(monitorLockObject); + } + else + { + // Notify one thread of results becoming available. + Monitor.Pulse(monitorLockObject); + } + } + }); + + while (_continueResources == null && !_terminateEvent.WaitOne(0)) + { + Monitor.Wait(monitorLockObject); + } + if (_continueResources == null) { // We've been aborted throw new BuildAbortedException(); } VerifyEntryInActiveOrWaitingState(); - return _continueResources.NumCores; + + int grantedCores = _continueResources.NumCores; + _continueResources = null; + return grantedCores; } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs index 6e63766bc8f..e22b4619587 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs @@ -366,9 +366,9 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Requests CPU resources from the scheduler. /// - int IRequestBuilderCallback.RequestCores(int requestedCores, bool waitForCores) + int IRequestBuilderCallback.RequestCores(object monitorLockObject, int requestedCores, bool waitForCores) { - return _requestBuilderCallback.RequestCores(requestedCores, waitForCores); + return _requestBuilderCallback.RequestCores(monitorLockObject, requestedCores, waitForCores); } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 8ceb6a72ece..005ed1c6e0a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -740,20 +740,26 @@ public int RequestCores(int requestedCores) IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; int coresAcquired = 0; + bool returningImplicitCore = false; if (_isImplicitCoreUsed) { - coresAcquired = builderCallback.RequestCores(requestedCores, waitForCores: true); + coresAcquired = builderCallback.RequestCores(_callbackMonitor, requestedCores, waitForCores: true); } - else if (requestedCores > 1) + else { - coresAcquired = builderCallback.RequestCores(requestedCores - 1, waitForCores: false); + _isImplicitCoreUsed = true; + returningImplicitCore = true; + if (requestedCores > 1) + { + coresAcquired = builderCallback.RequestCores(_callbackMonitor, requestedCores - 1, waitForCores: false); + } } _additionalAcquiredCores += coresAcquired; - if (!_isImplicitCoreUsed) + if (returningImplicitCore) { - // Pad the result with the one implicit core. This ensures that first call never blocks and always returns >= 1. - _isImplicitCoreUsed = true; + // Pad the result with the one implicit core if it was still available. + // This ensures that first call never blocks and always returns >= 1. coresAcquired++; } From d10923f2fd20d223c7a18769c852aeaed3163a29 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 26 Mar 2021 11:08:55 +0100 Subject: [PATCH 092/105] Release all cores on reacquire --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 005ed1c6e0a..154cec365a4 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -361,6 +361,10 @@ public void Yield() /// public void Reacquire() { + // Release all cores on reacquire. The assumption here is that the task is done with CPU intensive work at this point and forgetting + // to release explicitly granted cores when reacquiring the node may lead to deadlocks. + ReleaseAllCores(); + lock (_callbackMonitor) { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; From 3ac664273b2a08ae57ca41524dd6fb695a19b192 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 26 Mar 2021 11:37:14 +0100 Subject: [PATCH 093/105] Renames and tweaks --- .../RequestBuilder/RequestBuilder.cs | 21 +++++++++---------- .../Components/RequestBuilder/TaskHost.cs | 6 +++--- .../BackEnd/Components/Scheduler/Scheduler.cs | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 186c62461f5..5cbb746fc2a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -56,7 +56,7 @@ internal class RequestBuilder : IRequestBuilder, IRequestBuilderCallback, IBuild /// /// The resources granted when a build request entry continues. /// - private ResourceResponse _continueResources; + private ConcurrentQueue _continueResources; /// /// The task representing the currently-executing build request. @@ -116,6 +116,7 @@ internal RequestBuilder() _terminateEvent = new ManualResetEvent(false); _continueEvent = new AutoResetEvent(false); _continueWithResourcesEvent = new AutoResetEvent(false); + _continueResources = new ConcurrentQueue(); } /// @@ -244,7 +245,7 @@ public void ContinueRequestWithResources(ResourceResponse response) ErrorUtilities.VerifyThrow(!_continueWithResourcesEvent.WaitOne(0), "Request already continued"); VerifyEntryInActiveOrWaitingState(); - _continueResources = response; + _continueResources.Enqueue(response); _continueWithResourcesEvent.Set(); } @@ -516,21 +517,19 @@ public int RequestCores(object monitorLockObject, int requestedCores, bool waitF } }); - while (_continueResources == null && !_terminateEvent.WaitOne(0)) + while (_continueResources.IsEmpty) { + if (_terminateEvent.WaitOne(0)) + { + // We've been aborted + throw new BuildAbortedException(); + } Monitor.Wait(monitorLockObject); } - if (_continueResources == null) - { - // We've been aborted - throw new BuildAbortedException(); - } VerifyEntryInActiveOrWaitingState(); - int grantedCores = _continueResources.NumCores; - _continueResources = null; - return grantedCores; + return _continueResources.Dequeue().NumCores; } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 154cec365a4..97b171df52b 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -744,7 +744,7 @@ public int RequestCores(int requestedCores) IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; int coresAcquired = 0; - bool returningImplicitCore = false; + bool allocatingImplicitCore = false; if (_isImplicitCoreUsed) { coresAcquired = builderCallback.RequestCores(_callbackMonitor, requestedCores, waitForCores: true); @@ -752,7 +752,7 @@ public int RequestCores(int requestedCores) else { _isImplicitCoreUsed = true; - returningImplicitCore = true; + allocatingImplicitCore = true; if (requestedCores > 1) { coresAcquired = builderCallback.RequestCores(_callbackMonitor, requestedCores - 1, waitForCores: false); @@ -760,7 +760,7 @@ public int RequestCores(int requestedCores) } _additionalAcquiredCores += coresAcquired; - if (returningImplicitCore) + if (allocatingImplicitCore) { // Pad the result with the one implicit core if it was still available. // This ensures that first call never blocks and always returns >= 1. diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 8277256ac0f..9594b3185c8 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -1351,7 +1351,7 @@ private int GetAvailableCoresForExplicitRequests() { // At least one core is always implicitly granted to the node making the request. // If _nodeCoreAllocationWeight is more than zero, it can increase this value by the specified fraction of executing nodes. - int implicitlyGrantedCores = Math.Max(1, ((_schedulingData.ExecutingRequestsCount - 1) * _nodeCoreAllocationWeight) / 100); + int implicitlyGrantedCores = Math.Max(1, (_schedulingData.ExecutingRequestsCount * _nodeCoreAllocationWeight) / 100); // The number of explicitly granted cores is a sum of everything we've granted via RequestCores() so far across all nodes. int explicitlyGrantedCores = _schedulingData.ExplicitlyGrantedCores; From 059ac7b7a96e4f23312bf1b80921d18e25c3fe75 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 26 Mar 2021 14:29:15 +0100 Subject: [PATCH 094/105] Add unit tests to TaskHost_Tests --- .../BackEnd/BuildRequestEngine_Tests.cs | 17 ++- src/Build.UnitTests/BackEnd/TaskHost_Tests.cs | 110 +++++++++++++++++- .../Components/RequestBuilder/TaskHost.cs | 4 + src/Shared/UnitTests/MockEngine.cs | 17 +-- 4 files changed, 130 insertions(+), 18 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs index a509e6489f7..f81b683587b 100644 --- a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs @@ -268,6 +268,9 @@ private ProjectInstance CreateStandinProject() private AutoResetEvent _engineExceptionEvent; private Exception _engineException_Exception; + private AutoResetEvent _engineResourceRequestEvent; + private ResourceRequest _engineResourceRequest_Request; + private IBuildRequestEngine _engine; private IConfigCache _cache; private int _nodeRequestId; @@ -284,6 +287,7 @@ public BuildRequestEngine_Tests() _newRequestEvent = new AutoResetEvent(false); _newConfigurationEvent = new AutoResetEvent(false); _engineExceptionEvent = new AutoResetEvent(false); + _engineResourceRequestEvent = new AutoResetEvent(false); _engine = (IBuildRequestEngine)_host.GetComponent(BuildComponentType.RequestEngine); _cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); @@ -305,6 +309,7 @@ public void Dispose() _newRequestEvent.Dispose(); _newConfigurationEvent.Dispose(); _engineExceptionEvent.Dispose(); + _engineResourceRequestEvent.Dispose(); _host = null; } @@ -317,7 +322,7 @@ private void ConfigureEngine(IBuildRequestEngine engine) engine.OnRequestResumed += this.Engine_RequestResumed; engine.OnStatusChanged += this.Engine_EngineStatusChanged; engine.OnEngineException += this.Engine_Exception; - engine.OnResourceRequest += e => { }; // TODO + engine.OnResourceRequest += this.Engine_ResourceRequest; } /// @@ -592,5 +597,15 @@ private void Engine_Exception(Exception e) _engineException_Exception = e; _engineExceptionEvent.Set(); } + + /// + /// Callback for event raised when resources are requested. + /// + /// The resource request + private void Engine_ResourceRequest(ResourceRequest request) + { + _engineResourceRequest_Request = request; + _engineResourceRequestEvent.Set(); + } } } diff --git a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs index beb972e73fe..1db0b3935b2 100644 --- a/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHost_Tests.cs @@ -15,6 +15,7 @@ using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; using System.Threading.Tasks; using Xunit; +using Shouldly; namespace Microsoft.Build.UnitTests.BackEnd { @@ -705,6 +706,90 @@ public void TasksGetNoGlobalPropertiesIfNoneSpecified() mockLogger.AssertLogContains("Global property count: 0"); } + [Fact] + public void RequestCoresThrowsOnInvalidInput() + { + Assert.Throws(() => + { + _taskHost.RequestCores(0); + }); + + Assert.Throws(() => + { + _taskHost.RequestCores(-1); + }); + } + + [Fact] + public void RequestCoresUsesImplicitCore() + { + // If the request callback has no cores to grant, we still get 1 for the implicit core. + _mockRequestCallback.CoresToGrant = 0; + _taskHost.RequestCores(3).ShouldBe(1); + _mockRequestCallback.LastRequestedCores.ShouldBe(2); + _mockRequestCallback.LastWaitForCores.ShouldBeFalse(); + } + + [Fact] + public void RequestCoresUsesCoresFromRequestCallback() + { + // The request callback has 1 core to grant, we should see it returned from RequestCores. + _mockRequestCallback.CoresToGrant = 1; + _taskHost.RequestCores(3).ShouldBe(2); + _mockRequestCallback.LastRequestedCores.ShouldBe(2); + _mockRequestCallback.LastWaitForCores.ShouldBeFalse(); + + // Since we've used the implicit core, the second call will return only what the request callback gives us and may block. + _mockRequestCallback.CoresToGrant = 1; + _taskHost.RequestCores(3).ShouldBe(1); + _mockRequestCallback.LastRequestedCores.ShouldBe(3); + _mockRequestCallback.LastWaitForCores.ShouldBeTrue(); + } + + [Fact] + public void ReleaseCoresThrowsOnInvalidInput() + { + Assert.Throws(() => + { + _taskHost.ReleaseCores(0); + }); + + Assert.Throws(() => + { + _taskHost.ReleaseCores(-1); + }); + } + + [Fact] + public void ReleaseCoresReturnsCoresToRequestCallback() + { + _mockRequestCallback.CoresToGrant = 1; + _taskHost.RequestCores(3).ShouldBe(2); + + // We return one of two granted cores, the call passes through to the request callback. + _taskHost.ReleaseCores(1); + _mockRequestCallback.LastCoresToRelease.ShouldBe(1); + + // The implicit core is still allocated so a subsequent RequestCores call may block. + _taskHost.RequestCores(1); + _mockRequestCallback.LastWaitForCores.ShouldBeTrue(); + } + + [Fact] + public void ReleaseCoresReturnsImplicitCore() + { + _mockRequestCallback.CoresToGrant = 1; + _taskHost.RequestCores(3).ShouldBe(2); + + // We return both granted cores, one of them is returned to the request callback. + _taskHost.ReleaseCores(2); + _mockRequestCallback.LastCoresToRelease.ShouldBe(1); + + // The implicit core is not allocated anymore so a subsequent RequestCores call won't block. + _taskHost.RequestCores(1); + _mockRequestCallback.LastWaitForCores.ShouldBeFalse(); + } + #region Helper Classes /// @@ -1221,6 +1306,26 @@ internal class MockIRequestBuilderCallback : IRequestBuilderCallback, IRequestBu /// private BuildResult[] _buildResultsToReturn; + /// + /// The requestedCores argument passed to the last RequestCores call. + /// + public int LastRequestedCores { get; private set; } + + /// + /// The waitForCores argument passed to the last RequestCores call. + /// + public bool LastWaitForCores { get; private set; } + + /// + /// The value to be returned from the RequestCores call. + /// + public int CoresToGrant { get; set; } + + /// + /// The coresToRelease argument passed to the last ReleaseCores call. + /// + public int LastCoresToRelease { get; private set; } + /// /// Constructor which takes an array of build results to return from the BuildProjects method when it is called. /// @@ -1304,7 +1409,9 @@ public void ExitMSBuildCallbackState() /// public int RequestCores(object monitorLockObject, int requestedCores, bool waitForCores) { - return 0; + LastRequestedCores = requestedCores; + LastWaitForCores = waitForCores; + return CoresToGrant; } /// @@ -1312,6 +1419,7 @@ public int RequestCores(object monitorLockObject, int requestedCores, bool waitF /// public void ReleaseCores(int coresToRelease) { + LastCoresToRelease = coresToRelease; } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 97b171df52b..071385ff18d 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -739,6 +739,8 @@ public bool ShouldTreatWarningAsError(string warningCode) /// at least one core to become available. public int RequestCores(int requestedCores) { + ErrorUtilities.VerifyThrowArgumentOutOfRange(requestedCores > 0, nameof(requestedCores)); + lock (_callbackMonitor) { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; @@ -779,6 +781,8 @@ public int RequestCores(int requestedCores) /// granted and not yet released. public void ReleaseCores(int coresToRelease) { + ErrorUtilities.VerifyThrowArgumentOutOfRange(coresToRelease > 0, nameof(coresToRelease)); + lock (_callbackMonitor) { if (_isImplicitCoreUsed && coresToRelease > _additionalAcquiredCores) diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index a9ee96a69ea..431e8948cb0 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -6,7 +6,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; -using System.Threading; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; @@ -31,7 +30,7 @@ namespace Microsoft.Build.UnitTests * is somewhat of a no-no for task assemblies. * **************************************************************************/ - internal sealed class MockEngine : IBuildEngine8 + internal sealed class MockEngine : IBuildEngine7 { private readonly object _lockObj = new object(); // Protects _log, _output private readonly ITestOutputHelper _output; @@ -489,19 +488,5 @@ public object UnregisterTaskObject(object key, RegisteredTaskObjectLifetime life _objectCache.TryRemove(key, out object obj); return obj; } - - public int RequestCores(int requestedCores) - { - return 0; - } - - public void ReleaseCores(int coresToRelease) - { - } - - public bool ShouldTreatWarningAsError(string warningCode) - { - return false; - } } } From 1340f7c74a91479cef0e49d9df54238c566a276b Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 26 Mar 2021 22:16:48 +0100 Subject: [PATCH 095/105] Fix bug in scheduler where cores were not granted during build rundown --- .../BackEnd/Components/Scheduler/Scheduler.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 9594b3185c8..23ee8cf7080 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -663,7 +663,8 @@ private void ScheduleUnassignedRequests(List responses) } else { - // Nodes still have work, but we have no requests. Let them proceed. + // Nodes still have work, but we have no requests. Let them proceed and only handle resource requests. + HandlePendingResourceRequests(); TraceScheduler("{0}: Waiting for existing work to proceed.", schedulingTime); } @@ -1813,23 +1814,33 @@ private void ResolveRequestFromCacheAndResumeIfPossible(SchedulableRequest reque } /// - /// Determines which work is available which must be assigned to the nodes. This includes: - /// 1. Ready requests - those requests which can immediately resume executing. - /// 2. Requests which can continue because results are now available but we haven't distributed them. + /// Satisfies pending resource requests. Requests are pulled from the queue in FIFO fashion and granted as many cores + /// as possible, optimizing for maximum number of cores granted to a single request, not for maximum number of satisfied + /// requests. /// - private void ResumeRequiredWork(List responses) + private void HandlePendingResourceRequests() { - // If we have pending RequestCore calls, satisfy those first. while (_pendingRequestCoresCallbacks.Count > 0) { int availableCores = GetAvailableCoresForExplicitRequests(); if (availableCores == 0) { - break; + return; } TaskCompletionSource completionSource = _pendingRequestCoresCallbacks.Dequeue(); completionSource.SetResult(availableCores); } + } + + /// + /// Determines which work is available which must be assigned to the nodes. This includes: + /// 1. Ready requests - those requests which can immediately resume executing. + /// 2. Requests which can continue because results are now available but we haven't distributed them. + /// + private void ResumeRequiredWork(List responses) + { + // If we have pending RequestCore calls, satisfy those first. + HandlePendingResourceRequests(); // Resume any ready requests on the existing nodes. foreach (int nodeId in _availableNodes.Keys) From f2381add0021a68b887a2bf2e3ee7532fc8274be Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 26 Mar 2021 22:17:45 +0100 Subject: [PATCH 096/105] Rework RequestCores concurrency --- .../RequestBuilder/RequestBuilder.cs | 69 +++++++++---------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 5cbb746fc2a..ba3ba11fb34 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -43,20 +43,15 @@ internal class RequestBuilder : IRequestBuilder, IRequestBuilderCallback, IBuild /// private AutoResetEvent _continueEvent; - /// - /// The event to signal that this request should wake up from its wait state after granting resources. - /// - private AutoResetEvent _continueWithResourcesEvent; - /// /// The results used when a build request entry continues. /// private IDictionary _continueResults; /// - /// The resources granted when a build request entry continues. + /// Queue of actions to call when a resource request is responded to. /// - private ConcurrentQueue _continueResources; + private ConcurrentQueue> _pendingResourceRequests; /// /// The task representing the currently-executing build request. @@ -115,8 +110,7 @@ internal RequestBuilder() { _terminateEvent = new ManualResetEvent(false); _continueEvent = new AutoResetEvent(false); - _continueWithResourcesEvent = new AutoResetEvent(false); - _continueResources = new ConcurrentQueue(); + _pendingResourceRequests = new ConcurrentQueue>(); } /// @@ -242,11 +236,10 @@ public void ContinueRequestWithResources(ResourceResponse response) { ErrorUtilities.VerifyThrow(HasActiveBuildRequest, "Request not building"); ErrorUtilities.VerifyThrow(!_terminateEvent.WaitOne(0), "Request already terminated"); - ErrorUtilities.VerifyThrow(!_continueWithResourcesEvent.WaitOne(0), "Request already continued"); + ErrorUtilities.VerifyThrow(!_pendingResourceRequests.IsEmpty, "No pending resource requests"); VerifyEntryInActiveOrWaitingState(); - _continueResources.Enqueue(response); - _continueWithResourcesEvent.Set(); + _pendingResourceRequests.Dequeue()(response); } /// @@ -494,42 +487,42 @@ public void ExitMSBuildCallbackState() /// public int RequestCores(object monitorLockObject, int requestedCores, bool waitForCores) { + ErrorUtilities.VerifyThrow(Monitor.IsEntered(monitorLockObject), "Not running under the given lock"); VerifyIsNotZombie(); - RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, requestedCores, waitForCores)); - - WaitHandle[] handles = new WaitHandle[] { _terminateEvent, _continueWithResourcesEvent }; - Task.Run(() => + ResourceResponse responseObject = null; + using AutoResetEvent responseEvent = new AutoResetEvent(false); + _pendingResourceRequests.Enqueue((ResourceResponse response) => { - int waitResult = WaitHandle.WaitAny(handles); - lock (monitorLockObject) - { - if (waitResult == 0) - { - // Notify all threads of a build abort. - Monitor.PulseAll(monitorLockObject); - } - else - { - // Notify one thread of results becoming available. - Monitor.Pulse(monitorLockObject); - } - } + responseObject = response; + responseEvent.Set(); }); - while (_continueResources.IsEmpty) + RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, requestedCores, waitForCores)); + + WaitHandle[] waitHandles = new WaitHandle[] { _terminateEvent, responseEvent }; + int waitResult; + + // Drop the lock so that the same task can call ReleaseCores from other threads to unblock itself, and wait for the response. + Monitor.Exit(monitorLockObject); + try { - if (_terminateEvent.WaitOne(0)) - { - // We've been aborted - throw new BuildAbortedException(); - } - Monitor.Wait(monitorLockObject); + waitResult = WaitHandle.WaitAny(waitHandles); + } + finally + { + // Now re-take the lock before continuing. + Monitor.Enter(monitorLockObject); } VerifyEntryInActiveOrWaitingState(); + if (waitResult == 0) + { + // We've been aborted. + throw new BuildAbortedException(); + } - return _continueResources.Dequeue().NumCores; + return responseObject.NumCores; } /// From 605f6f273588493aa9006eec51a327daee38075b Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 26 Mar 2021 22:19:02 +0100 Subject: [PATCH 097/105] Fix ArgumentOutOfRangeException in ReleaseAllCores --- src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 071385ff18d..96d4a260389 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -806,10 +806,11 @@ public void ReleaseCores(int coresToRelease) /// internal void ReleaseAllCores() { - ReleaseCores(_additionalAcquiredCores + (_isImplicitCoreUsed ? 1 : 0)); - - _additionalAcquiredCores = 0; - _isImplicitCoreUsed = false; + int coresToRelease = _additionalAcquiredCores + (_isImplicitCoreUsed ? 1 : 0); + if (coresToRelease > 0) + { + ReleaseCores(coresToRelease); + } } #endregion From 876fef6455ce4c14a2db545250caf0bd7a97123a Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 26 Mar 2021 22:19:41 +0100 Subject: [PATCH 098/105] Add ResourceManagement_Tests --- .../BackEnd/BuildRequestEngine_Tests.cs | 1 - .../ResourceManagement_Tests.cs | 156 ++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/Tasks.UnitTests/ResourceManagement_Tests.cs diff --git a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs index f81b683587b..bd2e1172fe5 100644 --- a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs @@ -189,7 +189,6 @@ public void ContinueRequest() public void ContinueRequestWithResources(ResourceResponse response) { - // TODO } public void CancelRequest() diff --git a/src/Tasks.UnitTests/ResourceManagement_Tests.cs b/src/Tasks.UnitTests/ResourceManagement_Tests.cs new file mode 100644 index 00000000000..59c9a7fbb3a --- /dev/null +++ b/src/Tasks.UnitTests/ResourceManagement_Tests.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Build.Framework; +using Microsoft.Build.UnitTests; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using Shouldly; + +namespace Microsoft.Build.Tasks.UnitTests +{ + public class ResourceManagement_Tests + { + [Fact] + public void SingleCoreRequest() + { + var messages = AssertBuildSucceededAndGetMessages(@" + { + int grantedCores = BuildEngine8.RequestCores(1337); + Log.LogMessage(""Number of cores acquired: "" + grantedCores); + BuildEngine8.ReleaseCores(grantedCores); + }", ""); + + var filteredMessages = messages.Where(m => m.Message.StartsWith("Number of cores acquired: ")).ToArray(); + filteredMessages.Count().ShouldBe(1); + GetTrailingIntegerFromMessage(filteredMessages[0]).ShouldBeGreaterThan(0); + } + + [Fact] + public void SingleCoreRequestWithNoRelease() + { + var messages = AssertBuildSucceededAndGetMessages(@" + { + int grantedCores = BuildEngine8.RequestCores(1337); + Log.LogMessage(""Number of cores acquired: "" + grantedCores); + // Note that we're missing a call to ReleaseCores() so we rely on cores being released after the task is finished. + }", " "); + + var filteredMessages = messages.Where(m => m.Message.StartsWith("Number of cores acquired: ")).ToArray(); + filteredMessages.Count().ShouldBe(2); + + int grantedCores1 = GetTrailingIntegerFromMessage(filteredMessages[0]); + int grantedCores2 = GetTrailingIntegerFromMessage(filteredMessages[1]); + + // Both tasks were able to get the same number of cores because cores were auto-released. + grantedCores1.ShouldBeGreaterThan(0); + grantedCores2.ShouldBe(grantedCores1); + } + + [Fact] + public void SingleCoreRequestWithReacquire() + { + var messages = AssertBuildSucceededAndGetMessages(@" + { + int grantedCores1 = BuildEngine8.RequestCores(1337); + Log.LogMessage(""Number of cores acquired: "" + grantedCores1); + + BuildEngine8.Yield(); + // Reacquire releases all cores. + BuildEngine8.Reacquire(); + + int grantedCores2 = BuildEngine8.RequestCores(1337); + Log.LogMessage(""Number of cores acquired: "" + grantedCores2); + }", ""); + + var filteredMessages = messages.Where(m => m.Message.StartsWith("Number of cores acquired: ")).ToArray(); + filteredMessages.Count().ShouldBe(2); + + int grantedCores1 = GetTrailingIntegerFromMessage(filteredMessages[0]); + int grantedCores2 = GetTrailingIntegerFromMessage(filteredMessages[1]); + + // Both tasks were able to get the same number of cores because cores were auto-released. + grantedCores1.ShouldBeGreaterThan(0); + grantedCores2.ShouldBe(grantedCores1); + } + + [Fact] + public void MultipleCoreRequests() + { + // Exercise concurrent RequestCores() and ReleaseCores() calls. + AssertBuildSucceededAndGetMessages(@" + { + const int coresToAcquire = 1337; + int acquiredCores = 0; + int done = 0; + System.Threading.Thread requestThread = new System.Threading.Thread(() => + { + for (int i = 0; i < coresToAcquire; i++) + { + BuildEngine8.RequestCores(1); + System.Threading.Interlocked.Increment(ref acquiredCores); + } + System.Threading.Thread.VolatileWrite(ref done, 1); + }); + System.Threading.Thread releaseThread = new System.Threading.Thread(() => + { + while (System.Threading.Thread.VolatileRead(ref done) == 0 || System.Threading.Thread.VolatileRead(ref acquiredCores) > 0) + { + if (System.Threading.Thread.VolatileRead(ref acquiredCores) > 0) + { + BuildEngine8.ReleaseCores(1); + System.Threading.Interlocked.Decrement(ref acquiredCores); + } + else + { + System.Threading.Thread.Yield(); + } + } + }); + + // One thread is acquiring cores, the other is releasing them. The releasing thread is running with a lower + // priority to increase the chances of contention where all cores are allocated and RequestCores() blocks. + requestThread.Start(); + releaseThread.Priority = System.Threading.ThreadPriority.BelowNormal; + releaseThread.Start(); + + requestThread.Join(); + releaseThread.Join(); + }", ""); + } + + private List AssertBuildSucceededAndGetMessages(string taskCode, string targetContent) + { + string text = $@" + + + + + {taskCode} + + + + + + {targetContent} + +"; + using var env = TestEnvironment.Create(); + + var projectFile = env.CreateTestProjectWithFiles("test.proj", text); + var logger = projectFile.BuildProjectExpectSuccess(); + return logger.BuildMessageEvents; + } + + private int GetTrailingIntegerFromMessage(BuildMessageEventArgs msg) + { + string[] messageComponents = msg.Message.Split(' '); + int.TryParse(messageComponents.Last(), out int trailingInteger).ShouldBeTrue(); + return trailingInteger; + } + } +} From f9f018517bc222bf2735020aa115a7ea24a86b08 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Fri, 26 Mar 2021 22:58:36 +0100 Subject: [PATCH 099/105] Move state check and make tests Core-compatible --- .../BackEnd/Components/RequestBuilder/RequestBuilder.cs | 2 +- src/Tasks.UnitTests/ResourceManagement_Tests.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index ba3ba11fb34..a75916e7ce0 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -515,13 +515,13 @@ public int RequestCores(object monitorLockObject, int requestedCores, bool waitF Monitor.Enter(monitorLockObject); } - VerifyEntryInActiveOrWaitingState(); if (waitResult == 0) { // We've been aborted. throw new BuildAbortedException(); } + VerifyEntryInActiveOrWaitingState(); return responseObject.NumCores; } diff --git a/src/Tasks.UnitTests/ResourceManagement_Tests.cs b/src/Tasks.UnitTests/ResourceManagement_Tests.cs index 59c9a7fbb3a..5ab3c42ad3a 100644 --- a/src/Tasks.UnitTests/ResourceManagement_Tests.cs +++ b/src/Tasks.UnitTests/ResourceManagement_Tests.cs @@ -126,10 +126,11 @@ private List AssertBuildSucceededAndGetMessages(string ta - + + {taskCode} From 719425bfb57d990e4e38d97720c0794e812de63f Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Mon, 29 Mar 2021 11:33:46 +0200 Subject: [PATCH 100/105] PR feedback: Make instantiation of ResourceRequest more readable --- .../RequestBuilder/RequestBuilder.cs | 4 ++-- src/Build/BackEnd/Shared/ResourceRequest.cs | 24 ++++++++++--------- src/Build/BackEnd/Shared/ResourceResponse.cs | 3 ++- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index a75916e7ce0..24036744a7e 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -498,7 +498,7 @@ public int RequestCores(object monitorLockObject, int requestedCores, bool waitF responseEvent.Set(); }); - RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, requestedCores, waitForCores)); + RaiseResourceRequest(ResourceRequest.CreateAcquireRequest(_requestEntry.Request.GlobalRequestId, requestedCores, waitForCores)); WaitHandle[] waitHandles = new WaitHandle[] { _terminateEvent, responseEvent }; int waitResult; @@ -531,7 +531,7 @@ public int RequestCores(object monitorLockObject, int requestedCores, bool waitF public void ReleaseCores(int coresToRelease) { VerifyIsNotZombie(); - RaiseResourceRequest(new ResourceRequest(_requestEntry.Request.GlobalRequestId, coresToRelease)); + RaiseResourceRequest(ResourceRequest.CreateReleaseRequest(_requestEntry.Request.GlobalRequestId, coresToRelease)); } #endregion diff --git a/src/Build/BackEnd/Shared/ResourceRequest.cs b/src/Build/BackEnd/Shared/ResourceRequest.cs index da5b5dc7b8a..815eedb9200 100644 --- a/src/Build/BackEnd/Shared/ResourceRequest.cs +++ b/src/Build/BackEnd/Shared/ResourceRequest.cs @@ -6,7 +6,7 @@ namespace Microsoft.Build.BackEnd /// /// This packet is sent by a node to request or release resources from/to the scheduler. /// - internal class ResourceRequest : INodePacket + internal sealed class ResourceRequest : INodePacket { /// /// The global request id of the request which is asking for resources. @@ -38,25 +38,27 @@ internal ResourceRequest(ITranslator translator) } /// - /// Constructor for acquiring. + /// Private constructor, use CreateAcquireRequest or CreateReleaseRequest to make instances. /// - internal ResourceRequest(int globalRequestId, int numCores, bool isBlocking) + private ResourceRequest(bool isResourceAcquire, int globalRequestId, int numCores, bool isBlocking) { - _isResourceAcquire = true; + _isResourceAcquire = isResourceAcquire; _isBlocking = isBlocking; _globalRequestId = globalRequestId; _numCores = numCores; } /// - /// Constructor for releasing. + /// Factory method for acquiring. /// - internal ResourceRequest(int globalRequestId, int numCores) - { - _isResourceAcquire = false; - _globalRequestId = globalRequestId; - _numCores = numCores; - } + public static ResourceRequest CreateAcquireRequest(int globalRequestId, int numCores, bool isBlocking) + => new ResourceRequest(isResourceAcquire: true, globalRequestId, numCores, isBlocking); + + /// + /// Factory method for releasing. + /// + public static ResourceRequest CreateReleaseRequest(int globalRequestId, int numCores) + => new ResourceRequest(isResourceAcquire: false, globalRequestId, numCores, isBlocking: false); /// /// Returns the type of packet. diff --git a/src/Build/BackEnd/Shared/ResourceResponse.cs b/src/Build/BackEnd/Shared/ResourceResponse.cs index e603307d7c3..22fcefef30f 100644 --- a/src/Build/BackEnd/Shared/ResourceResponse.cs +++ b/src/Build/BackEnd/Shared/ResourceResponse.cs @@ -4,8 +4,9 @@ namespace Microsoft.Build.BackEnd { /// + /// This packet is sent by the scheduler in response to to grant resources to a node. /// - internal class ResourceResponse : INodePacket + internal sealed class ResourceResponse : INodePacket { /// /// The global request id of the request which is being responded to. From 2a6cabe225581e388b20f447557695fd7b193c81 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Mon, 29 Mar 2021 11:45:54 +0200 Subject: [PATCH 101/105] PR feedback: Move the new API to IBuildEngine9 --- .../net/Microsoft.Build.Framework.cs | 5 +++- .../netstandard/Microsoft.Build.Framework.cs | 5 +++- .../net/Microsoft.Build.Utilities.Core.cs | 1 + .../Microsoft.Build.Utilities.Core.cs | 1 + .../Components/RequestBuilder/TaskHost.cs | 26 +++++++++------- src/Framework/IBuildEngine8.cs | 14 --------- src/Framework/IBuildEngine9.cs | 25 ++++++++++++++++ src/MSBuild/OutOfProcTaskHostNode.cs | 30 +++++++++++-------- .../ResourceManagement_Tests.cs | 18 +++++------ src/Utilities/Task.cs | 5 ++++ 10 files changed, 81 insertions(+), 49 deletions(-) create mode 100644 src/Framework/IBuildEngine9.cs diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs index 4d21eeef9bb..1318a437f92 100644 --- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs @@ -215,10 +215,13 @@ public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, bool AllowFailureWithoutError { get; set; } } public partial interface IBuildEngine8 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7 + { + bool ShouldTreatWarningAsError(string warningCode); + } + public partial interface IBuildEngine9 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7, Microsoft.Build.Framework.IBuildEngine8 { void ReleaseCores(int coresToRelease); int RequestCores(int requestedCores); - bool ShouldTreatWarningAsError(string warningCode); } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { diff --git a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs index 1b57dbaf331..9d3b8a4e277 100644 --- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs @@ -215,10 +215,13 @@ public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, bool AllowFailureWithoutError { get; set; } } public partial interface IBuildEngine8 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7 + { + bool ShouldTreatWarningAsError(string warningCode); + } + public partial interface IBuildEngine9 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7, Microsoft.Build.Framework.IBuildEngine8 { void ReleaseCores(int coresToRelease); int RequestCores(int requestedCores); - bool ShouldTreatWarningAsError(string warningCode); } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { diff --git a/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs b/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs index de994107a7a..4491f300e5c 100644 --- a/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs +++ b/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs @@ -354,6 +354,7 @@ public abstract partial class Task : Microsoft.Build.Framework.ITask public Microsoft.Build.Framework.IBuildEngine6 BuildEngine6 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine7 BuildEngine7 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine8 BuildEngine8 { get { throw null; } } + public Microsoft.Build.Framework.IBuildEngine9 BuildEngine9 { get { throw null; } } protected string HelpKeywordPrefix { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskHost HostObject { get { throw null; } set { } } public Microsoft.Build.Utilities.TaskLoggingHelper Log { get { throw null; } } diff --git a/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs b/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs index ff4b2b17f1b..f0ad18e1545 100644 --- a/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs +++ b/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs @@ -199,6 +199,7 @@ public abstract partial class Task : Microsoft.Build.Framework.ITask public Microsoft.Build.Framework.IBuildEngine6 BuildEngine6 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine7 BuildEngine7 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine8 BuildEngine8 { get { throw null; } } + public Microsoft.Build.Framework.IBuildEngine9 BuildEngine9 { get { throw null; } } protected string HelpKeywordPrefix { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskHost HostObject { get { throw null; } set { } } public Microsoft.Build.Utilities.TaskLoggingHelper Log { get { throw null; } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 96d4a260389..6c0e60966d0 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -33,7 +33,7 @@ internal class TaskHost : #if FEATURE_APPDOMAIN MarshalByRefObject, #endif - IBuildEngine8 + IBuildEngine9 { /// /// True if the "secret" environment variable MSBUILDNOINPROCNODE is set. @@ -686,16 +686,6 @@ public void LogTelemetry(string eventName, IDictionary propertie private ICollection _warningsAsErrors; - /// - /// Additional cores granted to the task by the scheduler. Does not include the one implicit core automatically granted to all tasks. - /// - private int _additionalAcquiredCores = 0; - - /// - /// True if the one implicit core has been allocated by , false otherwise. - /// - private bool _isImplicitCoreUsed = false; - /// /// Contains all warnings that should be logged as errors. /// Non-null empty set when all warnings should be treated as errors. @@ -730,6 +720,20 @@ public bool ShouldTreatWarningAsError(string warningCode) return WarningsAsErrors.Count == 0 || WarningsAsErrors.Contains(warningCode); } + #endregion + + #region IBuildEngine9 Members + + /// + /// Additional cores granted to the task by the scheduler. Does not include the one implicit core automatically granted to all tasks. + /// + private int _additionalAcquiredCores = 0; + + /// + /// True if the one implicit core has been allocated by , false otherwise. + /// + private bool _isImplicitCoreUsed = false; + /// /// Allocates shared CPU resources. Called by a task when it's about to do potentially multi-threaded/multi-process work. /// diff --git a/src/Framework/IBuildEngine8.cs b/src/Framework/IBuildEngine8.cs index 5583eeba561..0caaad46678 100644 --- a/src/Framework/IBuildEngine8.cs +++ b/src/Framework/IBuildEngine8.cs @@ -9,20 +9,6 @@ namespace Microsoft.Build.Framework /// public interface IBuildEngine8 : IBuildEngine7 { - /// - /// If a task launches multiple parallel processes, it should ask how many cores it can use. - /// - /// The number of cores a task can potentially use. - /// The number of cores a task is allowed to use. - int RequestCores(int requestedCores); - - /// - /// A task should notify the build manager when all or some of the requested cores are not used anymore. - /// When task is finished, the cores it requested are automatically released. - /// - /// Number of cores no longer in use. - void ReleaseCores(int coresToRelease); - /// /// Determines whether the logging service will convert the specified /// warning code into an error. diff --git a/src/Framework/IBuildEngine9.cs b/src/Framework/IBuildEngine9.cs new file mode 100644 index 00000000000..6e44be92834 --- /dev/null +++ b/src/Framework/IBuildEngine9.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Build.Framework +{ + /// + /// This interface extends to provide resource management API to tasks. + /// + public interface IBuildEngine9 : IBuildEngine8 + { + /// + /// If a task launches multiple parallel processes, it should ask how many cores it can use. + /// + /// The number of cores a task can potentially use. + /// The number of cores a task is allowed to use. + int RequestCores(int requestedCores); + + /// + /// A task should notify the build manager when all or some of the requested cores are not used anymore. + /// When task is finished, the cores it requested are automatically released. + /// + /// Number of cores no longer in use. + void ReleaseCores(int coresToRelease); + } +} diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index 9a6db534f6e..fa626e3b70d 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -33,7 +33,7 @@ internal class OutOfProcTaskHostNode : #if CLR2COMPATIBILITY IBuildEngine3 #else - IBuildEngine8 + IBuildEngine9 #endif { /// @@ -468,6 +468,22 @@ public void LogTelemetry(string eventName, IDictionary propertie } #endregion + + #region IBuildEngine9 Implementation + + public int RequestCores(int requestedCores) + { + // No resource management in OOP nodes + throw new NotImplementedException(); + } + + public void ReleaseCores(int coresToRelease) + { + // No resource management in OOP nodes + throw new NotImplementedException(); + } + + #endregion #endif #region INodePacketFactory Members @@ -1175,17 +1191,5 @@ private void LogErrorFromResource(string messageResource) LogErrorEvent(error); } - - public int RequestCores(int requestedCores) - { - // No resource management in OOP nodes - throw new NotImplementedException(); - } - - public void ReleaseCores(int coresToRelease) - { - // No resource management in OOP nodes - throw new NotImplementedException(); - } } } diff --git a/src/Tasks.UnitTests/ResourceManagement_Tests.cs b/src/Tasks.UnitTests/ResourceManagement_Tests.cs index 5ab3c42ad3a..d75cdd4e120 100644 --- a/src/Tasks.UnitTests/ResourceManagement_Tests.cs +++ b/src/Tasks.UnitTests/ResourceManagement_Tests.cs @@ -17,9 +17,9 @@ public void SingleCoreRequest() { var messages = AssertBuildSucceededAndGetMessages(@" { - int grantedCores = BuildEngine8.RequestCores(1337); + int grantedCores = BuildEngine9.RequestCores(1337); Log.LogMessage(""Number of cores acquired: "" + grantedCores); - BuildEngine8.ReleaseCores(grantedCores); + BuildEngine9.ReleaseCores(grantedCores); }", ""); var filteredMessages = messages.Where(m => m.Message.StartsWith("Number of cores acquired: ")).ToArray(); @@ -32,7 +32,7 @@ public void SingleCoreRequestWithNoRelease() { var messages = AssertBuildSucceededAndGetMessages(@" { - int grantedCores = BuildEngine8.RequestCores(1337); + int grantedCores = BuildEngine9.RequestCores(1337); Log.LogMessage(""Number of cores acquired: "" + grantedCores); // Note that we're missing a call to ReleaseCores() so we rely on cores being released after the task is finished. }", " "); @@ -53,14 +53,14 @@ public void SingleCoreRequestWithReacquire() { var messages = AssertBuildSucceededAndGetMessages(@" { - int grantedCores1 = BuildEngine8.RequestCores(1337); + int grantedCores1 = BuildEngine9.RequestCores(1337); Log.LogMessage(""Number of cores acquired: "" + grantedCores1); - BuildEngine8.Yield(); + BuildEngine9.Yield(); // Reacquire releases all cores. - BuildEngine8.Reacquire(); + BuildEngine9.Reacquire(); - int grantedCores2 = BuildEngine8.RequestCores(1337); + int grantedCores2 = BuildEngine9.RequestCores(1337); Log.LogMessage(""Number of cores acquired: "" + grantedCores2); }", ""); @@ -88,7 +88,7 @@ public void MultipleCoreRequests() { for (int i = 0; i < coresToAcquire; i++) { - BuildEngine8.RequestCores(1); + BuildEngine9.RequestCores(1); System.Threading.Interlocked.Increment(ref acquiredCores); } System.Threading.Thread.VolatileWrite(ref done, 1); @@ -99,7 +99,7 @@ public void MultipleCoreRequests() { if (System.Threading.Thread.VolatileRead(ref acquiredCores) > 0) { - BuildEngine8.ReleaseCores(1); + BuildEngine9.ReleaseCores(1); System.Threading.Interlocked.Decrement(ref acquiredCores); } else diff --git a/src/Utilities/Task.cs b/src/Utilities/Task.cs index a9f3de6ff36..2ca6e4a10d9 100644 --- a/src/Utilities/Task.cs +++ b/src/Utilities/Task.cs @@ -99,6 +99,11 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// public IBuildEngine8 BuildEngine8 => (IBuildEngine8)BuildEngine; + /// + /// Retrieves the version of the build engine interface provided by the host. + /// + public IBuildEngine9 BuildEngine9 => (IBuildEngine9)BuildEngine; + /// /// The build engine sets this property if the host IDE has associated a host object with this particular task. /// From 8b53959d14ab726e9348122ae2d1b9dff62ab75d Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Mon, 29 Mar 2021 11:50:50 +0200 Subject: [PATCH 102/105] PR feedback: Add comments to RequestBuilder.RequestCores --- .../BackEnd/Components/RequestBuilder/RequestBuilder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs index 24036744a7e..7b7f60f06cc 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs @@ -490,6 +490,8 @@ public int RequestCores(object monitorLockObject, int requestedCores, bool waitF ErrorUtilities.VerifyThrow(Monitor.IsEntered(monitorLockObject), "Not running under the given lock"); VerifyIsNotZombie(); + // The task may be calling RequestCores from multiple threads and the call may be blocking, so in general, we have to maintain + // a queue of pending requests. ResourceResponse responseObject = null; using AutoResetEvent responseEvent = new AutoResetEvent(false); _pendingResourceRequests.Enqueue((ResourceResponse response) => @@ -500,10 +502,11 @@ public int RequestCores(object monitorLockObject, int requestedCores, bool waitF RaiseResourceRequest(ResourceRequest.CreateAcquireRequest(_requestEntry.Request.GlobalRequestId, requestedCores, waitForCores)); + // Wait for one of two events to be signaled: 1) The build was canceled, 2) The response to our request was received. WaitHandle[] waitHandles = new WaitHandle[] { _terminateEvent, responseEvent }; int waitResult; - // Drop the lock so that the same task can call ReleaseCores from other threads to unblock itself, and wait for the response. + // Drop the lock so that the same task can call ReleaseCores from other threads to unblock itself. Monitor.Exit(monitorLockObject); try { From 74046a0896f412435ded24ab8d1a8aa5d27ee5af Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Mon, 29 Mar 2021 12:25:22 +0200 Subject: [PATCH 103/105] PR feedback: Document callers of IRequestBuilderCallback.RequestCores/ReleaseCores --- src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs index e22b4619587..ba7547452ab 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs @@ -366,6 +366,7 @@ void IRequestBuilderCallback.ExitMSBuildCallbackState() /// /// Requests CPU resources from the scheduler. /// + /// This method is called from the . int IRequestBuilderCallback.RequestCores(object monitorLockObject, int requestedCores, bool waitForCores) { return _requestBuilderCallback.RequestCores(monitorLockObject, requestedCores, waitForCores); @@ -374,6 +375,7 @@ int IRequestBuilderCallback.RequestCores(object monitorLockObject, int requested /// /// Returns CPU resources to the scheduler. /// + /// This method is called from the . void IRequestBuilderCallback.ReleaseCores(int coresToRelease) { _requestBuilderCallback.ReleaseCores(coresToRelease); From 93db0c1c4a46659f03ac83b1ac2fb5cbbf3c804d Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Tue, 30 Mar 2021 10:00:56 +0200 Subject: [PATCH 104/105] PR feedback: Comment new environment variables --- src/Build/BackEnd/Components/Scheduler/Scheduler.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 23ee8cf7080..1e335cedb85 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -197,10 +197,16 @@ public Scheduler() } } + // Resource management tuning knobs: + // 1) MSBUILDCORELIMIT is the maximum number of cores we hand out via IBuildEngine9.RequestCores. + // Note that it is independent of build parallelism as given by /m on the command line. if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDCORELIMIT"), out _coreLimit) || _coreLimit <= 0) { _coreLimit = NativeMethodsShared.GetLogicalCoreCount(); } + // 1) MSBUILDNODECOREALLOCATIONWEIGHT is the weight with which executing nodes reduce the number of available cores. + // Example: If the weight is 50, _coreLimit is 8, and there are 4 nodes that are busy executing build requests, + // then the number of cores available via IBuildEngine9.RequestCores is 8 - (0.5 * 4) = 6. if (!int.TryParse(Environment.GetEnvironmentVariable("MSBUILDNODECOREALLOCATIONWEIGHT"), out _nodeCoreAllocationWeight) || _nodeCoreAllocationWeight <= 0 || _nodeCoreAllocationWeight > 100) From de74af6b218cb71807310f25adb864f9ad6e6677 Mon Sep 17 00:00:00 2001 From: Ladi Prosek Date: Tue, 30 Mar 2021 11:50:45 +0200 Subject: [PATCH 105/105] PR feedback: Add RequestCores/ReleaseCores logging --- .../Components/RequestBuilder/TaskHost.cs | 40 ++++++++++++++++--- src/Build/Resources/Strings.resx | 9 +++++ src/Build/Resources/xlf/Strings.cs.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.de.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.en.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.es.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.fr.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.it.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.ja.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.ko.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.pl.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.pt-BR.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.ru.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.tr.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.zh-Hans.xlf | 15 +++++++ src/Build/Resources/xlf/Strings.zh-Hant.xlf | 15 +++++++ 16 files changed, 253 insertions(+), 6 deletions(-) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 6c0e60966d0..88b32c65d1e 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -734,6 +734,11 @@ public bool ShouldTreatWarningAsError(string warningCode) /// private bool _isImplicitCoreUsed = false; + /// + /// Total number of cores granted to the task, including the one implicit core. + /// + private int TotalAcquiredCores => _additionalAcquiredCores + (_isImplicitCoreUsed ? 1 : 0); + /// /// Allocates shared CPU resources. Called by a task when it's about to do potentially multi-threaded/multi-process work. /// @@ -774,6 +779,11 @@ public int RequestCores(int requestedCores) } Debug.Assert(coresAcquired >= 1); + if (LoggingContext.IsValid) + { + LoggingContext.LogComment(MessageImportance.Low, "TaskAcquiredCores", _taskLoggingContext.TaskName, + requestedCores, coresAcquired, TotalAcquiredCores); + } return coresAcquired; } } @@ -789,18 +799,36 @@ public void ReleaseCores(int coresToRelease) lock (_callbackMonitor) { - if (_isImplicitCoreUsed && coresToRelease > _additionalAcquiredCores) + int coresBeingReleased = coresToRelease; + int previousTotalAcquiredCores = TotalAcquiredCores; + + if (_isImplicitCoreUsed && coresBeingReleased > _additionalAcquiredCores) { // Release the implicit core last, i.e. only if we're asked to release everything. - coresToRelease -= 1; + coresBeingReleased -= 1; _isImplicitCoreUsed = false; } - if (coresToRelease >= 1) + coresBeingReleased = Math.Min(coresBeingReleased, _additionalAcquiredCores); + if (coresBeingReleased >= 1) { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; - builderCallback.ReleaseCores(coresToRelease); - _additionalAcquiredCores -= coresToRelease; + builderCallback.ReleaseCores(coresBeingReleased); + _additionalAcquiredCores -= coresBeingReleased; + } + + if (LoggingContext.IsValid) + { + if (TotalAcquiredCores == previousTotalAcquiredCores - coresToRelease) + { + LoggingContext.LogComment(MessageImportance.Low, "TaskReleasedCores", _taskLoggingContext.TaskName, + coresToRelease, TotalAcquiredCores); + } + else + { + LoggingContext.LogComment(MessageImportance.Low, "TaskReleasedCoresWarning", _taskLoggingContext.TaskName, + coresToRelease, previousTotalAcquiredCores, TotalAcquiredCores); + } } } } @@ -810,7 +838,7 @@ public void ReleaseCores(int coresToRelease) /// internal void ReleaseAllCores() { - int coresToRelease = _additionalAcquiredCores + (_isImplicitCoreUsed ? 1 : 0); + int coresToRelease = TotalAcquiredCores; if (coresToRelease > 0) { ReleaseCores(coresToRelease); diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 8a8a77610d4..ed692c7d2c3 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1070,6 +1070,15 @@ Output Property: {0}={1} + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + Task "{0}" released {1} cores and now holds {2} cores total. + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Build continuing because "{0}" on the task "{1}" is set to "{2}". diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 6f48daf8d36..fb1577178b1 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: Úloha {0} vrátila false, ale do protokolu se nezaznamenala chyba. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 3e12dd793c1..af64cdfcad4 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: Die Aufgabe "{0}" hat FALSE zurückgegeben, jedoch keinen Fehler protokolliert. diff --git a/src/Build/Resources/xlf/Strings.en.xlf b/src/Build/Resources/xlf/Strings.en.xlf index 557613e2413..a030cd7e6bd 100644 --- a/src/Build/Resources/xlf/Strings.en.xlf +++ b/src/Build/Resources/xlf/Strings.en.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: The "{0}" task returned false but did not log an error. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index d2a6f93df72..8c22137cad3 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: La tarea "{0}" devolvió false, pero no registró un error. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 86246b0867f..6bd6ee632d3 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: la tâche "{0}" a retourné false mais n'a pas journalisé d'erreur. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 0dd8e7bce9d..0d6bbe712c2 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: l'attività "{0}" ha restituito false, ma non è stato registrato alcun errore. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 192beb71ced..1c1466e6602 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: "{0}" タスクから false が返されましたが、エラーがログに記録されませんでした。 diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 601f8e49a00..bc5d299bf55 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: "{0}" 작업이 false를 반환했지만 오류를 기록하지 않았습니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 777231c77a5..7322ba7adab 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: Zadanie „{0}” zwróciło wartość false, ale nie zarejestrowało błędu. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 04e1a3d63eb..be6f7c9a64f 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: A tarefa "{0}" retornou false, mas não registrou um erro. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 3da7d8b5bb3..4e34e3068f3 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: задача "{0}" возвратила значение false, но не зарегистрировала ошибку. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index cf33e768a2f..6d050218e1e 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: "{0}" görevi false değerini döndürdü ancak günlüğe hata kaydetmedi. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index b257204a1d9..f6d789754e2 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: “{0}”任务返回了 false,但未记录错误。 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 9d6e4a89f4a..90446337beb 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -298,6 +298,21 @@ LOCALIZATION: {0} is a file, {1} and {2} are semicolon delimited lists of messages + + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + Task "{0}" requested {1} cores, acquired {2} cores, and now holds {3} cores total. + + + + Task "{0}" released {1} cores and now holds {2} cores total. + Task "{0}" released {1} cores and now holds {2} cores total. + + + + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + Task "{0}" asked to release {1} cores while holding only {2} and now holds {3} cores total. + + MSB4181: The "{0}" task returned false but did not log an error. MSB4181: "{0}" 工作傳回了 False,但未記錄錯誤。