Skip to content

Commit 9d94176

Browse files
authored
Avoid locking TaskHostFactory-using assemblies (#7387)
Getting type information for a task that was supposed to be loaded in a TaskHost involves loading that assembly today. The point of TaskHost nodes is that they don't lock the assembly, and they disappear quickly, releasing the lock from when we actually need to load the assembly to execute it. This will mean that we don't lock the assembly to read its type information, actually complying with the user's likely intent. If the user asks for a TaskHost, use MetadataLoadContext to get its information rather than loading the assembly fully. Fixes #6461
1 parent b98a6be commit 9d94176

20 files changed

+338
-165
lines changed

documentation/specs/event-source.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ EventSource is primarily used to profile code. For MSBuild specifically, a major
1111
| Build | Sets up a BuildManager to receive build requests. |
1212
| BuildProject | Builds a project file. |
1313
| CachedSdkResolverServiceResolveSdk | The caching SDK resolver service is resolving an SDK. |
14+
| CreateLoadedType | Creates a LoadedType object from an assembly loaded via MetadataLoadContext. |
1415
| CopyUpToDate | Checks whether the Copy task needs to execute. |
1516
| Evaluate | Evaluates a project, running several other parts of MSBuild in the process. |
1617
| EvaluateCondition | Checks whether a condition is true and removes false conditionals. |

eng/Packages.props

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<PackageReference Update="System.Net.Http" Version="4.3.4" />
2222
<PackageReference Update="System.Memory" Version="4.5.5" />
2323
<PackageReference Update="System.Reflection.Metadata" Version="6.0.0" />
24+
<PackageReference Update="System.Reflection.MetadataLoadContext" Version="6.0.0" />
2425
<PackageReference Update="System.Resources.Extensions" Version="$(SystemResourcesExtensionsPackageVersion)" />
2526
<PackageReference Update="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafeVersion)" />
2627
<PackageReference Update="System.Security.Permissions" Version="6.0.0" />

src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public void CreatableByTaskFactoryMismatchedIdentity()
220220
public void VerifyGetTaskParameters()
221221
{
222222
TaskPropertyInfo[] propertyInfos = _taskFactory.GetTaskParameters();
223-
LoadedType comparisonType = new LoadedType(typeof(TaskToTestFactories), _loadInfo);
223+
LoadedType comparisonType = new LoadedType(typeof(TaskToTestFactories), _loadInfo, typeof(TaskToTestFactories).GetTypeInfo().Assembly);
224224
PropertyInfo[] comparisonInfo = comparisonType.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
225225
Assert.Equal(comparisonInfo.Length, propertyInfos.Length);
226226

src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1161,7 +1161,7 @@ private void InitializeHost(bool throwOnExecute)
11611161
#else
11621162
AssemblyLoadInfo loadInfo = AssemblyLoadInfo.Create(typeof(TaskBuilderTestTask.TaskBuilderTestTaskFactory).GetTypeInfo().FullName, null);
11631163
#endif
1164-
LoadedType loadedType = new LoadedType(typeof(TaskBuilderTestTask.TaskBuilderTestTaskFactory), loadInfo);
1164+
LoadedType loadedType = new LoadedType(typeof(TaskBuilderTestTask.TaskBuilderTestTaskFactory), loadInfo, typeof(TaskBuilderTestTask.TaskBuilderTestTaskFactory).GetTypeInfo().Assembly);
11651165

11661166
TaskBuilderTestTask.TaskBuilderTestTaskFactory taskFactory = new TaskBuilderTestTask.TaskBuilderTestTaskFactory();
11671167
taskFactory.ThrowOnExecute = throwOnExecute;

src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs

+60-36
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem;
2525
using Task = System.Threading.Tasks.Task;
26+
using System.Linq;
2627

2728
#nullable disable
2829

@@ -256,12 +257,12 @@ void ITaskExecutionHost.InitializeForTask(IBuildEngine2 buildEngine, TargetLoggi
256257

257258
TaskRequirements requirements = TaskRequirements.None;
258259

259-
if (_taskFactoryWrapper.TaskFactoryLoadedType.HasSTAThreadAttribute())
260+
if (_taskFactoryWrapper.TaskFactoryLoadedType.HasSTAThreadAttribute)
260261
{
261262
requirements |= TaskRequirements.RequireSTAThread;
262263
}
263264

264-
if (_taskFactoryWrapper.TaskFactoryLoadedType.HasLoadInSeparateAppDomainAttribute())
265+
if (_taskFactoryWrapper.TaskFactoryLoadedType.HasLoadInSeparateAppDomainAttribute)
265266
{
266267
requirements |= TaskRequirements.RequireSeparateAppDomain;
267268

@@ -399,6 +400,14 @@ bool ITaskExecutionHost.GatherTaskOutputs(string parameterName, ElementLocation
399400
try
400401
{
401402
TaskPropertyInfo parameter = _taskFactoryWrapper.GetProperty(parameterName);
403+
foreach (TaskPropertyInfo prop in _taskFactoryWrapper.TaskFactoryLoadedType.Properties)
404+
{
405+
if (prop.Name.Equals(parameterName, StringComparison.OrdinalIgnoreCase))
406+
{
407+
parameter = prop;
408+
break;
409+
}
410+
}
402411

403412
// flag an error if we find a parameter that has no .NET property equivalent
404413
ProjectErrorUtilities.VerifyThrowInvalidProject
@@ -420,17 +429,14 @@ bool ITaskExecutionHost.GatherTaskOutputs(string parameterName, ElementLocation
420429
_taskName
421430
);
422431

423-
// grab the outputs from the task's designated output parameter (which is a .NET property)
424-
Type type = parameter.PropertyType;
425-
426432
EnsureParameterInitialized(parameter, _batchBucket.Lookup);
427433

428-
if (TaskParameterTypeVerifier.IsAssignableToITask(type))
434+
if (parameter.IsAssignableToITask)
429435
{
430436
ITaskItem[] outputs = GetItemOutputs(parameter);
431437
GatherTaskItemOutputs(outputTargetIsItem, outputTargetName, outputs, parameterLocation, parameter);
432438
}
433-
else if (TaskParameterTypeVerifier.IsValueTypeOutputParameter(type))
439+
else if (parameter.IsValueTypeOutputParameter)
434440
{
435441
string[] outputs = GetValueOutputs(parameter);
436442
GatherArrayStringAndValueOutputs(outputTargetIsItem, outputTargetName, outputs, parameterLocation, parameter);
@@ -897,12 +903,14 @@ private TaskFactoryWrapper FindTaskInRegistry(IDictionary<string, string> taskId
897903
// Map to an intrinsic task, if necessary.
898904
if (String.Equals(returnClass.TaskFactory.TaskType.FullName, "Microsoft.Build.Tasks.MSBuild", StringComparison.OrdinalIgnoreCase))
899905
{
900-
returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(MSBuild)), new LoadedType(typeof(MSBuild), AssemblyLoadInfo.Create(typeof(TaskExecutionHost).GetTypeInfo().Assembly.FullName, null)), _taskName, null);
906+
Assembly taskExecutionHostAssembly = typeof(TaskExecutionHost).GetTypeInfo().Assembly;
907+
returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(MSBuild)), new LoadedType(typeof(MSBuild), AssemblyLoadInfo.Create(taskExecutionHostAssembly.FullName, null), taskExecutionHostAssembly), _taskName, null);
901908
_intrinsicTasks[_taskName] = returnClass;
902909
}
903910
else if (String.Equals(returnClass.TaskFactory.TaskType.FullName, "Microsoft.Build.Tasks.CallTarget", StringComparison.OrdinalIgnoreCase))
904911
{
905-
returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(CallTarget)), new LoadedType(typeof(CallTarget), AssemblyLoadInfo.Create(typeof(TaskExecutionHost).GetTypeInfo().Assembly.FullName, null)), _taskName, null);
912+
Assembly taskExecutionHostAssembly = typeof(TaskExecutionHost).GetTypeInfo().Assembly;
913+
returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(CallTarget)), new LoadedType(typeof(CallTarget), AssemblyLoadInfo.Create(taskExecutionHostAssembly.FullName, null), taskExecutionHostAssembly), _taskName, null);
906914
_intrinsicTasks[_taskName] = returnClass;
907915
}
908916
}
@@ -1008,12 +1016,43 @@ out bool parameterSet
10081016
try
10091017
{
10101018
// check if the task has a .NET property corresponding to the parameter
1011-
TaskPropertyInfo parameter = _taskFactoryWrapper.GetProperty(parameterName);
1019+
LoadedType loadedType = _taskFactoryWrapper.TaskFactoryLoadedType;
1020+
int indexOfParameter = -1;
1021+
for (int i = 0; i < loadedType.Properties.Length; i++)
1022+
{
1023+
if (loadedType.Properties[i].Name.Equals(parameterName))
1024+
{
1025+
indexOfParameter = i;
1026+
break;
1027+
}
1028+
}
10121029

1013-
if (parameter != null)
1030+
// For most tasks, finding the parameter in our list of known properties is equivalent to
1031+
// saying the task was properly invoked, as far as this parameter is concerned. However,
1032+
// that is not true for CodeTaskFactories like RoslynCodeTaskFactory. In that case, they
1033+
// will often have a list of parameters under the UsingTask declaration. Fortunately, if
1034+
// your TaskFactory is RoslynCodeTaskFactory, it isn't TaskHostFactory, which means the
1035+
// types are fully loaded at this stage, and we can access them as we had in the past.
1036+
TaskPropertyInfo parameter = null;
1037+
Type parameterType = null;
1038+
if (indexOfParameter != -1)
1039+
{
1040+
parameter = loadedType.Properties[indexOfParameter];
1041+
parameterType = Type.GetType(
1042+
loadedType.PropertyAssemblyQualifiedNames?[indexOfParameter] ??
1043+
parameter.PropertyType.AssemblyQualifiedName);
1044+
}
1045+
else
10141046
{
1015-
Type parameterType = parameter.PropertyType;
1047+
parameter = _taskFactoryWrapper.GetProperty(parameterName);
1048+
if (parameter != null)
1049+
{
1050+
parameterType = Type.GetType(parameter.PropertyType.AssemblyQualifiedName);
1051+
}
1052+
}
10161053

1054+
if (parameter != null)
1055+
{
10171056
EnsureParameterInitialized(parameter, _batchBucket.Lookup);
10181057

10191058
// try to set the parameter
@@ -1068,30 +1107,15 @@ out parameterSet
10681107
else
10691108
{
10701109
// flag an error if we find a parameter that has no .NET property equivalent
1071-
if (_taskFactoryWrapper.TaskFactoryLoadedType.LoadedAssembly is null)
1072-
{
1073-
_taskLoggingContext.LogError
1074-
(
1075-
new BuildEventFileInfo( parameterLocation ),
1076-
"UnexpectedTaskAttribute",
1077-
parameterName,
1078-
_taskName,
1079-
_taskFactoryWrapper.TaskFactoryLoadedType.Type.Assembly.FullName,
1080-
_taskFactoryWrapper.TaskFactoryLoadedType.Type.Assembly.Location
1081-
);
1082-
}
1083-
else
1084-
{
1085-
_taskLoggingContext.LogError
1086-
(
1087-
new BuildEventFileInfo( parameterLocation ),
1088-
"UnexpectedTaskAttribute",
1089-
parameterName,
1090-
_taskName,
1091-
_taskFactoryWrapper.TaskFactoryLoadedType.LoadedAssembly.FullName,
1092-
_taskFactoryWrapper.TaskFactoryLoadedType.LoadedAssembly.Location
1093-
);
1094-
}
1110+
_taskLoggingContext.LogError
1111+
(
1112+
new BuildEventFileInfo( parameterLocation ),
1113+
"UnexpectedTaskAttribute",
1114+
parameterName,
1115+
_taskName,
1116+
_taskFactoryWrapper.TaskFactoryLoadedType.LoadedAssemblyName.FullName,
1117+
_taskFactoryWrapper.TaskFactoryLoadedType.Path
1118+
);
10951119
}
10961120
}
10971121
catch (AmbiguousMatchException)

src/Build/Instance/ReflectableTaskPropertyInfo.cs

+16
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ internal ReflectableTaskPropertyInfo(PropertyInfo propertyInfo)
5252
_propertyInfo = propertyInfo;
5353
}
5454

55+
/// <summary>
56+
/// Initializes a new <see cref="ReflectableTaskPropertyInfo"/> with three precomputed parameters. This is specifically
57+
/// used with MetadataLoadContext, as these parameters cannot be computed for the property type passed in directly but
58+
/// rather the relevant base type.
59+
/// </summary>
60+
internal ReflectableTaskPropertyInfo(PropertyInfo propertyInfo, bool output, bool required, bool isAssignableToITaskItemType)
61+
: base(
62+
propertyInfo.Name,
63+
propertyInfo.PropertyType,
64+
output,
65+
required)
66+
{
67+
_propertyInfo = propertyInfo;
68+
IsAssignableToITask = isAssignableToITaskItemType;
69+
}
70+
5571
/// <summary>
5672
/// Gets or sets the reflection-produced PropertyInfo.
5773
/// </summary>

src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs

+3-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Linq;
67
using System.Reflection;
78
#if FEATURE_APPDOMAIN
89
using System.Threading.Tasks;
@@ -149,14 +150,7 @@ public bool Initialize(string taskName, IDictionary<string, string> factoryIdent
149150
/// </summary>
150151
public TaskPropertyInfo[] GetTaskParameters()
151152
{
152-
PropertyInfo[] infos = _loadedType.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
153-
var propertyInfos = new TaskPropertyInfo[infos.Length];
154-
for (int i = 0; i < infos.Length; i++)
155-
{
156-
propertyInfos[i] = new ReflectableTaskPropertyInfo(infos[i]);
157-
}
158-
159-
return propertyInfos;
153+
return _loadedType.Properties;
160154
}
161155

162156
/// <summary>
@@ -279,7 +273,7 @@ string taskProjectFile
279273
{
280274
ErrorUtilities.VerifyThrowArgumentLength(taskName, nameof(taskName));
281275
_taskName = taskName;
282-
_loadedType = _typeLoader.Load(taskName, loadInfo);
276+
_loadedType = _typeLoader.Load(taskName, loadInfo, _taskHostFactoryExplicitlyRequested);
283277
ProjectErrorUtilities.VerifyThrowInvalidProject(_loadedType != null, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, String.Empty);
284278
}
285279
catch (TargetInvocationException e)

src/Build/Instance/TaskRegistry.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ ElementLocation elementLocation
436436
targetLoggingContext.LogComment(MessageImportance.Low, "TaskFoundFromFactory", taskName, taskFactory.Name);
437437
}
438438

439-
if (taskFactory.TaskFactoryLoadedType.HasSTAThreadAttribute())
439+
if (taskFactory.TaskFactoryLoadedType.HasSTAThreadAttribute)
440440
{
441441
targetLoggingContext.LogComment(MessageImportance.Low, "TaskNeedsSTA", taskName);
442442
}

src/Build/Microsoft.Build.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<PackageReference Include="System.Text.Json" />
3636

3737
<PackageReference Include="System.Reflection.Metadata" Condition="'$(MonoBuild)' == 'true'" />
38+
<PackageReference Include="System.Reflection.MetadataLoadContext" />
3839

3940
<PackageReference Include="Microsoft.IO.Redist" Condition="'$(FeatureMSIORedist)' == 'true'" />
4041
</ItemGroup>

src/Framework/MSBuildEventSource.cs

+24
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,30 @@ public void SdkResolverServiceLoadResolversStop(string manifestName, int resolve
623623
WriteEvent(84, manifestName, resolverCount);
624624
}
625625

626+
[Event(85, Keywords = Keywords.All)]
627+
public void CreateLoadedTypeStart(string assemblyName)
628+
{
629+
WriteEvent(85, assemblyName);
630+
}
631+
632+
[Event(86, Keywords = Keywords.All)]
633+
public void CreateLoadedTypeStop(string assemblyName)
634+
{
635+
WriteEvent(86, assemblyName);
636+
}
637+
638+
[Event(87, Keywords = Keywords.All)]
639+
public void LoadAssemblyAndFindTypeStart()
640+
{
641+
WriteEvent(87);
642+
}
643+
644+
[Event(88, Keywords = Keywords.All)]
645+
public void LoadAssemblyAndFindTypeStop(string assemblyPath, int numberOfPublicTypesSearched)
646+
{
647+
WriteEvent(88, assemblyPath, numberOfPublicTypesSearched);
648+
}
649+
626650
#endregion
627651
}
628652
}

src/Framework/TaskPropertyInfo.cs

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System;
5+
using System.Reflection;
56

67
#nullable disable
78

@@ -26,6 +27,9 @@ public TaskPropertyInfo(string name, Type typeOfParameter, bool output, bool req
2627
PropertyType = typeOfParameter;
2728
Output = output;
2829
Required = required;
30+
Type elementType = typeOfParameter.IsArray ? typeOfParameter.GetElementType() : typeOfParameter;
31+
IsValueTypeOutputParameter = elementType.GetTypeInfo().IsValueType || elementType.FullName.Equals("System.String");
32+
IsAssignableToITask = typeof(ITaskItem).IsAssignableFrom(elementType);
2933
}
3034

3135
/// <summary>
@@ -62,5 +66,8 @@ public TaskPropertyInfo(string name, Type typeOfParameter, bool output, bool req
6266
/// Whether the Log and LogItemMetadata properties have been assigned already.
6367
/// </summary>
6468
internal bool Initialized = false;
69+
70+
internal bool IsValueTypeOutputParameter { get; private set; }
71+
internal bool IsAssignableToITask { get; set; }
6572
}
6673
}

src/MSBuild/MSBuild.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
<Link>FileUtilities.cs</Link>
7777
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
7878
</Compile>
79+
<Compile Include="..\Build\Instance\ReflectableTaskPropertyInfo.cs" />
7980
<Compile Include="..\Shared\FileUtilitiesRegex.cs">
8081
<Link>FileUtilitiesRegex.cs</Link>
8182
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>

src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ IDictionary<string, TaskParameter> taskParams
115115
try
116116
{
117117
TypeLoader typeLoader = new TypeLoader(TaskLoader.IsTaskClass);
118-
taskType = typeLoader.Load(taskName, AssemblyLoadInfo.Create(null, taskLocation));
118+
taskType = typeLoader.Load(taskName, AssemblyLoadInfo.Create(null, taskLocation), false);
119119
}
120120
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
121121
{
@@ -133,7 +133,7 @@ IDictionary<string, TaskParameter> taskParams
133133
}
134134

135135
OutOfProcTaskHostTaskResult taskResult;
136-
if (taskType.HasSTAThreadAttribute())
136+
if (taskType.HasSTAThreadAttribute)
137137
{
138138
#if FEATURE_APARTMENT_STATE
139139
taskResult = InstantiateAndExecuteTaskInSTAThread(oopTaskHostNode, taskType, taskName, taskLocation, taskFile, taskLine, taskColumn,

src/MSBuild/app.amd64.config

+10
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@
9191
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
9292
<codeBase version="4.1.4.0" href="..\System.Numerics.Vectors.dll"/>
9393
</dependentAssembly>
94+
<dependentAssembly>
95+
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
96+
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
97+
<codeBase version="6.0.0.0" href="..\System.Reflection.Metadata.dll" />
98+
</dependentAssembly>
99+
<dependentAssembly>
100+
<assemblyIdentity name="System.Reflection.MetadataLoadContext" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
101+
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
102+
<codeBase version="6.0.0.0" href="..\System.Reflection.MetadataLoadContext.dll" />
103+
</dependentAssembly>
94104
<dependentAssembly>
95105
<assemblyIdentity name="System.Resources.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
96106
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />

0 commit comments

Comments
 (0)