Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce cross-process resource management for tasks #5859

Merged
merged 118 commits into from Mar 30, 2021
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
f48f3d9
Initial draft of changes.
benvillalobos Feb 25, 2020
bcccfea
Modified mockengine to support ibuildengine7
benvillalobos Feb 26, 2020
73b3466
Simpler null checks
benvillalobos Feb 28, 2020
a8df6ac
Merge remote-tracking branch 'upstream/master' into 74-cpp-parallel
rainersigwald Mar 4, 2020
963a785
Clean up API surface
rainersigwald Mar 4, 2020
e1a30e6
Tasks type name
rainersigwald Mar 5, 2020
055116a
simple project
rainersigwald Mar 5, 2020
10f6eed
Simplify if-appdomain in NodeConfiguration
rainersigwald Mar 5, 2020
aa3f385
Checkpoint
rainersigwald Mar 6, 2020
57a5c93
Remove FEATURE_VARIOUS_EXCEPTIONS
rainersigwald Mar 6, 2020
9229122
Checkpoint: works on full only, doesn't properly block when all resou…
rainersigwald Mar 9, 2020
b919834
Make TaskHost.MarkAsInactive work on Core
rainersigwald Mar 9, 2020
01953aa
Tweak test to show cross-process handling
rainersigwald Mar 12, 2020
1183e87
Introduce RequireCores
rainersigwald Mar 12, 2020
0437c32
WIP
rainersigwald Mar 12, 2020
1bf3017
Revert "WIP"
rainersigwald Mar 12, 2020
1ada7c2
Horrible pile of WIP hacks to debug hang
rainersigwald Apr 20, 2020
e943655
Revert "Horrible pile of WIP hacks to debug hang"
rainersigwald Apr 20, 2020
ca6697d
Move requiring core to ExecuteInstantiatedTask
rainersigwald Apr 20, 2020
63a324b
Merge remote-tracking branch 'upstream/master' into exp/resource-mana…
rainersigwald Apr 27, 2020
09618ec
Release core when yielding (hopefully working around hang when many t…
rainersigwald May 1, 2020
5a7ad2b
WIP: new semaphore name per session (by default)
rainersigwald Jun 10, 2020
141c57c
Merge remote-tracking branch 'upstream/master' into exp/resource-mana…
rainersigwald Jun 11, 2020
007d9f3
Release a core when calling BuildProjectFiles
rainersigwald Jun 16, 2020
6b2be21
Switch expression for MockHost.GetComponent
rainersigwald Jun 16, 2020
5d49876
Resource manager in MockHost
rainersigwald Jun 16, 2020
0486730
Just don't do resource management on non-Windows
rainersigwald Jun 16, 2020
2f85924
Delete bogus tests
rainersigwald Jun 16, 2020
5e4b035
Doc for RequestCores
rainersigwald Jun 17, 2020
a9d37a2
Merge remote-tracking branch 'upstream/master' into exp/resource-mana…
rainersigwald Jun 17, 2020
43bdec0
Merge remote-tracking branch 'upstream/master' into exp/resource-mana…
rainersigwald Jul 15, 2020
479cdfc
Add BlockingWaitForCore
rainersigwald Jul 15, 2020
9db29bb
Treat resources as a separate pool; don't auto-acquire for tasks
rainersigwald Jul 15, 2020
e1c3bff
fixup! Add BlockingWaitForCore
rainersigwald Jul 15, 2020
58fa355
Doc updates
rainersigwald Jul 15, 2020
a6a9d6d
Merge remote-tracking branch 'upstream/master' into exp/resource-mana…
rainersigwald Sep 16, 2020
91fe07a
Block for at least one core in RequestCores
rainersigwald Sep 16, 2020
c9b3c78
Merge remote-tracking branch 'upstream/master' into exp/resource-mana…
rainersigwald Oct 21, 2020
ba2db9a
Remove BlockingWaitForCore() since it's now redundant
rainersigwald Oct 21, 2020
cc60df9
fixup! Block for at least one core in RequestCores
rainersigwald Oct 21, 2020
0ab0481
Move to IBuildEngine8 since 7 shipped already
rainersigwald Oct 21, 2020
f1e26c3
remove test-project.proj
rainersigwald Nov 3, 2020
dfced2f
Remove SemaphoreCPUTask
rainersigwald Nov 3, 2020
ed6a404
??
rainersigwald Nov 3, 2020
4545858
sort usings in MockHost
rainersigwald Nov 5, 2020
3de11c0
fixup! Treat resources as a separate pool; don't auto-acquire for tasks
rainersigwald Nov 5, 2020
608bed7
fixup! Remove BlockingWaitForCore() since it's now redundant
rainersigwald Nov 5, 2020
7a3da6b
fixup! Treat resources as a separate pool; don't auto-acquire for tasks
rainersigwald Nov 5, 2020
d87903f
Remove RequireCores
rainersigwald Nov 5, 2020
9a83f35
Better non-Windows behavior
rainersigwald Nov 5, 2020
77344e2
fixup! Move to IBuildEngine8 since 7 shipped already
rainersigwald Nov 5, 2020
7fffd46
Generalize MockEngine semaphore
rainersigwald Nov 5, 2020
7d8b359
Return nullable int to indicate whether resource management is possible
rainersigwald Dec 7, 2020
82a25c4
fixup! Remove RequireCores
rainersigwald Dec 7, 2020
c6d1a8a
Merge remote-tracking branch 'upstream/master' into HEAD
rainersigwald Dec 8, 2020
b8b52cc
Nix whitespace-only changes in ProjectCollection
rainersigwald Nov 20, 2020
f3e347e
fixup! fixup! Remove BlockingWaitForCore() since it's now redundant
rainersigwald Nov 20, 2020
ab384aa
Task whitespace fixes
rainersigwald Nov 20, 2020
a9d8061
Update doc for non-Windows behavior
rainersigwald Nov 20, 2020
25ec10c
Update doc with better example, caveats
rainersigwald Nov 20, 2020
b362600
Whitespace fixes in TaskBuilder
rainersigwald Nov 20, 2020
b830c49
Whitespace cleanup in TaskHost
rainersigwald Nov 20, 2020
bb2d947
Clarity in MockHost
rainersigwald Nov 20, 2020
59af842
fixup! fixup! fixup! Remove BlockingWaitForCore() since it's now redu…
rainersigwald Nov 20, 2020
cacc2ac
Log resource requests/releases
rainersigwald Nov 23, 2020
3c0a8e7
Move clamp on release to service layer
rainersigwald Dec 8, 2020
ded86d5
Switch system off more gracefully on non-Windows
rainersigwald Dec 8, 2020
ad77314
Merge remote-tracking branch 'upstream/master' into exp/resource-mana…
rainersigwald Dec 14, 2020
0c0c0b4
Release nodes on reacquire
rainersigwald Dec 15, 2020
a16ca54
Merge remote-tracking branch 'upstream/master' into exp/resource-mana…
rainersigwald Dec 15, 2020
1a497d7
Merge remote-tracking branch 'upstream/master' into exp/resource-mana…
rainersigwald Dec 15, 2020
7a4e1e0
Implicit core for nonblocking 1 return
rainersigwald Jan 5, 2021
3d2575c
Allow environment variable MSBUILDRESOURCEMANAGEROVERSUBSCRIPTION to …
rainersigwald Jan 5, 2021
c416bf5
Revert "Release nodes on reacquire"
rainersigwald Jan 5, 2021
444ffde
WIP: acquire/release resources in the scheduler
rainersigwald Jan 15, 2021
761fb6a
Update documentation/specs/resource-management.md
cdmihai Jan 15, 2021
2fb5789
Merge remote-tracking branch 'dotnet/master' into exp/resource-manage…
ladipro Feb 17, 2021
2ce2af8
Remove Semaphore-based logic
ladipro Feb 19, 2021
e3439e4
Plumbing to pass ResourceRequest to scheduler and ResourceResponse ba…
ladipro Feb 22, 2021
1653806
Add missing files: ResourceRequest.cs, ResourceResponse.cs
ladipro Feb 22, 2021
6d27a28
Plumbing fixes
ladipro Feb 24, 2021
df40170
Implement scheduling policy
ladipro Feb 24, 2021
3d03f86
Use 'implicit core' always, not only when scheduler returns 0
ladipro Mar 1, 2021
ba264da
Use different limits for scheduling and explicit core requests
ladipro Mar 1, 2021
d12bd75
Make RequestCores ignore the executing request count
ladipro Mar 5, 2021
1a6326e
Subtract one from _coreLimit
ladipro Mar 5, 2021
0b1a487
Change return value of RequestCores to int (null is no longer used)
ladipro Mar 12, 2021
d38500b
Do not assume that RequestCores calls come only from Executing requests
ladipro Mar 12, 2021
445ec33
Make RequestCores block and wait for at least one core
ladipro Mar 16, 2021
a98adee
Revert "Use 'implicit core' always, not only when scheduler returns 0"
ladipro Mar 16, 2021
15121be
Make the first RequestCores call non-blocking (via 'implicit' core)
ladipro Mar 16, 2021
02bce34
Make the implicit core the last one to release (LIFO)
ladipro Mar 16, 2021
b382aab
Introduce MSBUILDNODECOREALLOCATIONWEIGHT
ladipro Mar 16, 2021
8dbe055
Merge remote-tracking branch 'dotnet/main' into exp/resource-management
ladipro Mar 19, 2021
14c5d3d
Allow calling RequestCores/ReleaseCores after Yield
ladipro Mar 23, 2021
aad6f76
Update resource-management.md
ladipro Mar 24, 2021
3a554b8
Comments, renames, and tweaks
ladipro Mar 24, 2021
63d782e
Remove the now unused ResourceManagerService
ladipro Mar 24, 2021
874ecf3
Comments, renames, and tweaks
ladipro Mar 25, 2021
8ed06ad
Comments, renames, and tweaks
ladipro Mar 25, 2021
97e9f5d
Revert string changes
ladipro Mar 25, 2021
531ee86
Tweaks in Scheduler.cs
ladipro Mar 25, 2021
27865a8
Refactor SchedulingData & SchedulableRequest, don't consider nodes wi…
ladipro Mar 25, 2021
8016cf9
Don't wait for ResourceResponse under a lock
ladipro Mar 26, 2021
d10923f
Release all cores on reacquire
ladipro Mar 26, 2021
3ac6642
Renames and tweaks
ladipro Mar 26, 2021
059ac7b
Add unit tests to TaskHost_Tests
ladipro Mar 26, 2021
1340f7c
Fix bug in scheduler where cores were not granted during build rundown
ladipro Mar 26, 2021
f2381ad
Rework RequestCores concurrency
ladipro Mar 26, 2021
605f6f2
Fix ArgumentOutOfRangeException in ReleaseAllCores
ladipro Mar 26, 2021
876fef6
Add ResourceManagement_Tests
ladipro Mar 26, 2021
f9f0185
Move state check and make tests Core-compatible
ladipro Mar 26, 2021
719425b
PR feedback: Make instantiation of ResourceRequest more readable
ladipro Mar 29, 2021
2a6cabe
PR feedback: Move the new API to IBuildEngine9
ladipro Mar 29, 2021
8b53959
PR feedback: Add comments to RequestBuilder.RequestCores
ladipro Mar 29, 2021
74046a0
PR feedback: Document callers of IRequestBuilderCallback.RequestCores…
ladipro Mar 29, 2021
93db0c1
PR feedback: Comment new environment variables
ladipro Mar 30, 2021
de74af6
PR feedback: Add RequestCores/ReleaseCores logging
ladipro Mar 30, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions documentation/specs/resource-management.md
@@ -0,0 +1,41 @@
# 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.
cdmihai marked this conversation as resolved.
Show resolved Hide resolved

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`.
Copy link
Member

Choose a reason for hiding this comment

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

This section is well-written, but it reads to me as general statement of purpose --> example --> actually, we only care about the example. Although cl.exe was the motivating example, I imagine others will start using it later, so I'd focus on describing this generally rather than diving into a specific example in the first section. On a related note, are you planning to make documentation not in the specs folder? If so, this may be a moot point.


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 (`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, and a task can optionally return a subset by calling `ReleaseCores`.

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.

Task `Yield()`ing has no effect on the resources held by a task.
Copy link
Member

Choose a reason for hiding this comment

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

Now you mention it, is this the right choice? If all tasks opt into this, that would mean you're forcing some of the cores to be idle for no reason.


## 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`, 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.
Copy link
Member

Choose a reason for hiding this comment

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

Where is this sample task? I imagined something like:
// Construct 20x1000 array of data
// Request 20 cores
// Create new Thread[20]
// Only start the first threads
// Join them all, and as they finish, start new ones running.

The last step is a little messy, but it can probably be cleaned up by making callback functions:

            Thread[] threads = new Thread[20];
             for (int i = 0; i < 20; i++)
                      // Give threads work;
             int i = 0;
             void run(i) { thread[i].Join(); thread[i++].Start(); Task.Run(run); }
             for (; i < coresReceived; i++) {
                      thread[i].Start();
                      Task.Run(run);

Or, even better, something similar using async. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

I deleted the sample task because it was no longer useful--but we need one for tests so I'll have to write another.

I don't think it's feasible to provide a single example here. If you're using tasks/the threadpool, you might not want to use this, since the threadpool has its own ideas of resource management (which I believe but can't confirm go cross-process). And the work you'd likely do with dedicated threads is probably fairly different from the work to launch multiple processes. The actual C++ tasks have their own fairly complicated approach that isn't a great example.


```C#
int allowedParallelism = BuildEngine7.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.

## 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.
Copy link
Member

Choose a reason for hiding this comment

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

Should it always return 1? I imagine users wouldn't use more cores than they're given unless users complain.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you should either return 0 (indicating that there is no RM) or min of what is requested and the number of cores, which will be pretty much what we have today without RM.

Copy link
Member Author

Choose a reason for hiding this comment

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

With the change to blocking/get-at-least-1, I think immediately returning 1 makes the most sense here--and that's what I actually implemented; this doc was stale. Do you think the default for an "enlightened" task should be to overburden the machine, rather than underburden it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think returning 1 is confusing as different tasks would do different things with and without RM and they need to know if RM is actually working or not. Maybe we should just add an explicit method for this, like "IsRMSupported", and then it would be not so important what we return here.

Copy link
Member

Choose a reason for hiding this comment

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

I guess it depends on whether we expect users are going to inspect the output before using it. If they intend to immediately use it, returning 1 or min(requested, #cores) makes sense for underloading or overloading the machine respectively. If they're going to inspect it, -1 is easiest as an indicator of "failure" that can't have other meanings like "all cores in use."

Expand Up @@ -211,6 +211,11 @@ 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
{
void ReleaseCores(int coresToRelease);
int RequestCores(int requestedCores);
}
public partial interface ICancelableTask : Microsoft.Build.Framework.ITask
{
void Cancel();
Expand Down
Expand Up @@ -211,6 +211,11 @@ 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
{
void ReleaseCores(int coresToRelease);
int RequestCores(int requestedCores);
}
public partial interface ICancelableTask : Microsoft.Build.Framework.ITask
{
void Cancel();
Expand Down
Expand Up @@ -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; } }
Expand Down
Expand Up @@ -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; } }
Expand Down
1 change: 1 addition & 0 deletions ref/Microsoft.Build/net/Microsoft.Build.cs
Expand Up @@ -996,6 +996,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 { } }
Expand Down
1 change: 1 addition & 0 deletions ref/Microsoft.Build/netstandard/Microsoft.Build.cs
Expand Up @@ -991,6 +991,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 { } }
Expand Down
7 changes: 7 additions & 0 deletions src/Build.UnitTests/BackEnd/MockHost.cs
Expand Up @@ -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;
Expand Down Expand Up @@ -59,6 +60,8 @@ internal class MockHost : MockLoggingService, IBuildComponentHost, IBuildCompone

private ISdkResolverService _sdkResolverService;

private readonly ResourceManagerService _taskResourceManager;

#region SystemParameterFields

#endregion;
Expand Down Expand Up @@ -102,6 +105,9 @@ public MockHost(BuildParameters buildParameters)

_sdkResolverService = new MockSdkResolverService();
((IBuildComponent)_sdkResolverService).InitializeComponent(this);

_taskResourceManager = new ResourceManagerService();
((IBuildComponent)_taskResourceManager).InitializeComponent(this);
}

/// <summary>
Expand Down Expand Up @@ -170,6 +176,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),
};
}
Expand Down
8 changes: 8 additions & 0 deletions src/Build/BackEnd/BuildManager/BuildParameters.cs
Expand Up @@ -213,6 +213,7 @@ public class BuildParameters : ITranslatable
private string[] _inputResultsCacheFiles;

private string _outputResultsCacheFile;
private string _resourceManagerSemaphoreName = $"MSBuild.{Guid.NewGuid().ToString()}";

/// <summary>
/// Constructor for those who intend to set all properties themselves.
Expand Down Expand Up @@ -770,6 +771,12 @@ public string OutputResultsCacheFile
set => _outputResultsCacheFile = value;
}

public string ResourceManagerSemaphoreName
{
get => _resourceManagerSemaphoreName;
set => _resourceManagerSemaphoreName = value;
}
rainersigwald marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Determines whether MSBuild will save the results of builds after EndBuild to speed up future builds.
/// </summary>
Expand Down Expand Up @@ -838,6 +845,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.
Expand Down
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Build/BackEnd/Components/IBuildComponentHost.cs
Expand Up @@ -128,6 +128,11 @@ internal enum BuildComponentType
/// The SDK resolution service.
/// </summary>
SdkResolverService,

/// <summary>
/// Resource manager for tasks to use via <see cref="Microsoft.Build.Framework.IBuildEngine8.RequestCores(int)"/>.
/// </summary>
TaskResourceManager,
}

/// <summary>
Expand Down
18 changes: 8 additions & 10 deletions src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs
Expand Up @@ -338,11 +338,9 @@ private async Task<WorkUnitResult> ExecuteTask(TaskExecutionMode mode, Lookup lo
{
_taskExecutionHost.CleanupForTask();

#if FEATURE_APPDOMAIN
taskHost?.MarkAsInactive();
#endif

// 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)
Expand Down Expand Up @@ -404,14 +402,14 @@ private async Task<WorkUnitResult> 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<string, string> taskIdentityParameters = GatherTaskIdentityParameters(bucket.Expander);
TaskRequirements? requirements = _taskExecutionHost.FindTask(taskIdentityParameters);
if (requirements != null)
Expand Down Expand Up @@ -514,15 +512,15 @@ private async Task<WorkUnitResult> ExecuteBucket(TaskHost taskHost, ItemBucket b
/// </summary>
private IDictionary<string, string> GatherTaskIdentityParameters(Expander<ProjectPropertyInstance, ProjectItemInstance> 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<string, string> 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<string, string>(StringComparer.OrdinalIgnoreCase);
Expand Down Expand Up @@ -821,7 +819,7 @@ private async Task<WorkUnitResult> 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));
}
Expand Down Expand Up @@ -888,7 +886,7 @@ private async Task<WorkUnitResult> 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"))
Expand Down