Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class AssemblyTaskFactory_Tests
/// </summary>
public AssemblyTaskFactory_Tests()
{
SetupTaskFactory(null, false);
SetupTaskFactory(null, false, false);
}

#region AssemblyTaskFactory
Expand Down Expand Up @@ -338,7 +338,7 @@ public void VerifyMatchingUsingTaskParametersDontLaunchTaskHost1()
taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any);
taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any);

SetupTaskFactory(taskParameters, false /* don't want task host */);
SetupTaskFactory(taskParameters, false /* don't want task host */, false);

createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null,
#if FEATURE_APPDOMAIN
Expand Down Expand Up @@ -371,7 +371,7 @@ public void VerifyMatchingUsingTaskParametersDontLaunchTaskHost2()
taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any);
taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.GetCurrentMSBuildArchitecture());

SetupTaskFactory(taskParameters, false /* don't want task host */);
SetupTaskFactory(taskParameters, false /* don't want task host */, false);

createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null,
#if FEATURE_APPDOMAIN
Expand Down Expand Up @@ -403,7 +403,7 @@ public void VerifyMatchingParametersDontLaunchTaskHost()
IDictionary<string, string> factoryParameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
factoryParameters.Add(XMakeAttributes.runtime, XMakeAttributes.GetCurrentMSBuildRuntime());

SetupTaskFactory(factoryParameters, false /* don't want task host */);
SetupTaskFactory(factoryParameters, false /* don't want task host */, false);

IDictionary<string, string> taskParameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture);
Expand Down Expand Up @@ -439,7 +439,7 @@ public void VerifyNonmatchingUsingTaskParametersLaunchTaskHost()
taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr2);
taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any);

SetupTaskFactory(taskParameters, false /* don't want task host */);
SetupTaskFactory(taskParameters, false /* don't want task host */, false);

createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null,
#if FEATURE_APPDOMAIN
Expand Down Expand Up @@ -502,7 +502,7 @@ public void VerifyNonmatchingParametersLaunchTaskHost()
IDictionary<string, string> factoryParameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
factoryParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr2);

SetupTaskFactory(factoryParameters, false /* don't want task host */);
SetupTaskFactory(factoryParameters, false /* don't want task host */, false);

IDictionary<string, string> taskParameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any);
Expand Down Expand Up @@ -534,7 +534,7 @@ public void VerifyExplicitlyLaunchTaskHost()
ITask createdTask = null;
try
{
SetupTaskFactory(null, true /* want task host */);
SetupTaskFactory(null, true /* want task host */, true);

createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null,
#if FEATURE_APPDOMAIN
Expand Down Expand Up @@ -567,7 +567,7 @@ public void VerifyExplicitlyLaunchTaskHostEvenIfParametersMatch1()
taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any);
taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any);

SetupTaskFactory(taskParameters, true /* want task host */);
SetupTaskFactory(taskParameters, true /* want task host */, isTaskHostFactory: true);

createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null,
#if FEATURE_APPDOMAIN
Expand Down Expand Up @@ -596,7 +596,7 @@ public void VerifyExplicitlyLaunchTaskHostEvenIfParametersMatch2()
ITask createdTask = null;
try
{
SetupTaskFactory(null, true /* want task host */);
SetupTaskFactory(null, true /* want task host */, true);

IDictionary<string, string> taskParameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any);
Expand Down Expand Up @@ -631,7 +631,7 @@ public void VerifySameFactoryCanGenerateDifferentTaskInstances()
factoryParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any);
factoryParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any);

SetupTaskFactory(factoryParameters, explicitlyLaunchTaskHost: false);
SetupTaskFactory(factoryParameters, explicitlyLaunchTaskHost: false, isTaskHostFactory: false);

try
{
Expand Down Expand Up @@ -680,14 +680,19 @@ public void VerifySameFactoryCanGenerateDifferentTaskInstances()
/// Abstract out the creation of the new AssemblyTaskFactory with default task, and
/// with some basic validation.
/// </summary>
private void SetupTaskFactory(IDictionary<string, string> factoryParameters, bool explicitlyLaunchTaskHost)
private void SetupTaskFactory(IDictionary<string, string> factoryParameters, bool explicitlyLaunchTaskHost = false, bool isTaskHostFactory = false)
{
_taskFactory = new AssemblyTaskFactory();
#if FEATURE_ASSEMBLY_LOCATION
_loadInfo = AssemblyLoadInfo.Create(null, Assembly.GetAssembly(typeof(TaskToTestFactories)).Location);
#else
_loadInfo = AssemblyLoadInfo.Create(typeof(TaskToTestFactories).GetTypeInfo().Assembly.FullName, null);
#endif
if (explicitlyLaunchTaskHost)
{
factoryParameters ??= new Dictionary<string, string>();
factoryParameters.Add(Internal.Constants.TaskHostExplicitlyRequested, "true");
}
_loadedType = _taskFactory.InitializeFactory(_loadInfo, "TaskToTestFactories", new Dictionary<string, TaskPropertyInfo>(), string.Empty, factoryParameters, explicitlyLaunchTaskHost, null, ElementLocation.Create("NONE"), String.Empty);
Assert.True(_loadedType.Assembly.Equals(_loadInfo)); // "Expected the AssemblyLoadInfo to be equal"
}
Expand Down
29 changes: 29 additions & 0 deletions src/Build.UnitTests/BackEnd/ProcessIdTaskSidecar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
#nullable disable

namespace Microsoft.Build.UnitTests
{
/// <summary>
/// This task was created for https://github.com/dotnet/msbuild/issues/3141
/// </summary>
public class ProcessIdTaskSidecar : Task
{
[Output]
public int Pid { get; set; }

/// <summary>
/// Log the id for this process.
/// </summary>
/// <returns></returns>
public override bool Execute()
{
Pid = Process.GetCurrentProcess().Id;
return true;
}
}
}
99 changes: 88 additions & 11 deletions src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,114 @@ public TaskHostFactory_Tests(ITestOutputHelper testOutputHelper)
_output = testOutputHelper;
}

[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(true, true)]
public void TaskNodesDieAfterBuild(bool taskHostFactorySpecified, bool envVariableSpecified)
{
using (TestEnvironment env = TestEnvironment.Create())
{

string taskFactory = taskHostFactorySpecified ? "TaskHostFactory" : "AssemblyTaskFactory";
string pidTaskProject = $@"
<Project>
<UsingTask TaskName=""ProcessIdTask"" AssemblyName=""Microsoft.Build.Engine.UnitTests"" TaskFactory=""{taskFactory}"" />
<Target Name='AccessPID'>
<ProcessIdTask>
<Output PropertyName=""PID"" TaskParameter=""Pid"" />
</ProcessIdTask>
</Target>
</Project>";
TransientTestFile project = env.CreateFile("testProject.csproj", pidTaskProject);

if (envVariableSpecified)
{
env.SetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC", "1");
}
ProjectInstance projectInstance = new(project.Path);

projectInstance.Build().ShouldBeTrue();
string processId = projectInstance.GetPropertyValue("PID");
string.IsNullOrEmpty(processId).ShouldBeFalse();
Int32.TryParse(processId, out int pid).ShouldBeTrue();
Process.GetCurrentProcess().Id.ShouldNotBe(pid);

if (taskHostFactorySpecified)
{
try
{
Process taskHostNode = Process.GetProcessById(pid);
taskHostNode.WaitForExit(3000).ShouldBeTrue($"The executed MSBuild Version: {projectInstance.GetProperty("MSBuildVersion")}");
}
// We expect the TaskHostNode to exit quickly. If it exits before Process.GetProcessById, it will throw an ArgumentException.
catch (ArgumentException e)
{
e.Message.ShouldBe($"Process with an Id of {pid} is not running.");
}
}
else
{
// This is the sidecar TaskHost case - it should persist after build is done. So we need to clean up and kill it ourselves.
Process taskHostNode = Process.GetProcessById(pid);
taskHostNode.WaitForExit(3000).ShouldBeFalse($"The executed MSBuild Version: {projectInstance.GetProperty("MSBuildVersion")}");
taskHostNode.Kill();
}
}
}

[Fact]
public void TaskNodesDieAfterBuild()
public void TransiendAndSidecarNodeCanCoexist()
{
using (TestEnvironment env = TestEnvironment.Create())
{
string pidTaskProject = $@"
<Project>
<UsingTask TaskName=""ProcessIdTask"" AssemblyName=""Microsoft.Build.Engine.UnitTests"" TaskFactory=""TaskHostFactory"" />
<Target Name='AccessPID'>
<ProcessIdTask>
<Output PropertyName=""PID"" TaskParameter=""Pid"" />
</ProcessIdTask>
</Target>
<UsingTask TaskName=""ProcessIdTask"" AssemblyName=""Microsoft.Build.Engine.UnitTests"" TaskFactory=""TaskHostFactory"" />
<UsingTask TaskName=""ProcessIdTaskSidecar"" AssemblyName=""Microsoft.Build.Engine.UnitTests"" TaskFactory=""AssemblyTaskFactory"" />

<Target Name='AccessPID'>
<ProcessIdTask>
<Output PropertyName=""PID"" TaskParameter=""Pid"" />
</ProcessIdTask>
<ProcessIdTaskSidecar>
<Output PropertyName=""PID2"" TaskParameter=""Pid"" />
</ProcessIdTaskSidecar>
</Target>
</Project>";

TransientTestFile project = env.CreateFile("testProject.csproj", pidTaskProject);


env.SetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC", "1");
ProjectInstance projectInstance = new(project.Path);

projectInstance.Build().ShouldBeTrue();
string processId = projectInstance.GetPropertyValue("PID");
string processIdSidecar = projectInstance.GetPropertyValue("PID2");
processIdSidecar.ShouldNotBe(processId, "Each task should have it's own TaskHost node.");

string.IsNullOrEmpty(processId).ShouldBeFalse();
Int32.TryParse(processId, out int pid).ShouldBeTrue();
Process.GetCurrentProcess().Id.ShouldNotBe<int>(pid);
Int32.TryParse(processIdSidecar, out int pidSidecar).ShouldBeTrue();

Process.GetCurrentProcess().Id.ShouldNotBe(pid);


try
{
Process taskHostNode = Process.GetProcessById(pid);
taskHostNode.WaitForExit(2000).ShouldBeTrue();
Process taskHostNode1 = Process.GetProcessById(pid);
taskHostNode1.WaitForExit(3000).ShouldBeTrue("The node should be dead since this is the transient case.");
}
// We expect the TaskHostNode to exit quickly. If it exits before Process.GetProcessById, it will throw an ArgumentException.
catch (ArgumentException e)
{
// We expect the TaskHostNode to exit quickly. If it exits before Process.GetProcessById, it will throw an ArgumentException.
e.Message.ShouldBe($"Process with an Id of {pid} is not running.");
}
// This is the sidecar TaskHost case - it should persist after build is done. So we need to clean up and kill it ourselves.
Process taskHostNode2 = Process.GetProcessById(pidSidecar);
taskHostNode2.WaitForExit(3000).ShouldBeFalse($"The node should be alife since it is the sidecar node.");
taskHostNode2.Kill();
}
}

Comment thread
SimaTian marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ internal bool CreateNode(HandshakeOptions hostContext, INodePacketFactory factor

// if runtime host path is null it means we don't have MSBuild.dll path resolved and there is no need to include it in the command line arguments.
string commandLineArgsPlaceholder = "{0} /nologo /nodemode:2 /nodereuse:{1} /low:{2} ";
bool enableNodeReuse = ComponentHost.BuildParameters.EnableNodeReuse && Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.NodeReuse);
Comment thread
YuliiaKovalova marked this conversation as resolved.

IList<NodeContext> nodeContexts;
int nodeId = (int)hostContext;
Expand All @@ -590,7 +591,7 @@ internal bool CreateNode(HandshakeOptions hostContext, INodePacketFactory factor
// There is always one task host per host context so we always create just 1 one task host node here.
nodeContexts = GetNodes(
runtimeHostPath,
string.Format(commandLineArgsPlaceholder, msbuildAssemblyPath, ComponentHost.BuildParameters.EnableNodeReuse, ComponentHost.BuildParameters.LowPriority),
string.Format(commandLineArgsPlaceholder, msbuildAssemblyPath, enableNodeReuse, ComponentHost.BuildParameters.LowPriority),
nodeId,
this,
new Handshake(hostContext),
Expand All @@ -614,7 +615,7 @@ internal bool CreateNode(HandshakeOptions hostContext, INodePacketFactory factor

nodeContexts = GetNodes(
msbuildLocation,
string.Format(commandLineArgsPlaceholder, string.Empty, ComponentHost.BuildParameters.EnableNodeReuse, ComponentHost.BuildParameters.LowPriority),
string.Format(commandLineArgsPlaceholder, string.Empty, enableNodeReuse, ComponentHost.BuildParameters.LowPriority),
nodeId,
this,
new Handshake(hostContext),
Expand Down
14 changes: 11 additions & 3 deletions src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using ElementLocation = Microsoft.Build.Construction.ElementLocation;
using TargetLoggingContext = Microsoft.Build.BackEnd.Logging.TargetLoggingContext;
using TaskLoggingContext = Microsoft.Build.BackEnd.Logging.TaskLoggingContext;
using Microsoft.Build.Internal;

#nullable disable

Expand Down Expand Up @@ -68,6 +69,8 @@ internal class AssemblyTaskFactory : ITaskFactory2
/// </summary>
private TaskLoggingContext _taskLoggingContext;

private bool _isTaskHostFactory;

#endregion

/// <summary>
Expand Down Expand Up @@ -252,7 +255,7 @@ internal LoadedType InitializeFactory(
IDictionary<string, TaskPropertyInfo> taskParameters,
string taskElementContents,
IDictionary<string, string> taskFactoryIdentityParameters,
bool taskHostFactoryExplicitlyRequested,
bool taskHostExplicitlyRequested,
TargetLoggingContext targetLoggingContext,
ElementLocation elementLocation,
string taskProjectFile)
Expand All @@ -265,7 +268,11 @@ internal LoadedType InitializeFactory(
_factoryIdentityParameters = new Dictionary<string, string>(taskFactoryIdentityParameters, StringComparer.OrdinalIgnoreCase);
}

_taskHostFactoryExplicitlyRequested = taskHostFactoryExplicitlyRequested;
_taskHostFactoryExplicitlyRequested = taskHostExplicitlyRequested;

_isTaskHostFactory = (taskFactoryIdentityParameters != null
&& taskFactoryIdentityParameters.TryGetValue(Constants.TaskHostExplicitlyRequested, out string isTaskHostFactory)
&& isTaskHostFactory.Equals("true", StringComparison.OrdinalIgnoreCase));

try
{
Expand Down Expand Up @@ -364,7 +371,8 @@ internal ITask CreateTaskInstance(ElementLocation taskLocation, TaskLoggingConte
taskLoggingContext,
buildComponentHost,
mergedParameters,
_loadedType
_loadedType,
_isTaskHostFactory
#if FEATURE_APPDOMAIN
, appDomainSetup
#endif
Expand Down
Loading