Skip to content

Commit 355d87e

Browse files
CopilotAR-May
andauthored
Fix race condition in TaskRegistry/TypeLoader when building with /mt /m mode (#12653)
Fixes: #12735 #12645 ## Context A race condition in the TypeLoader class caused `System.ObjectDisposedException` crashes when building projects with `/mt /m` (multi-threaded mode). The error occurred when multiple threads simultaneously loaded task types using MetadataLoadContext: ``` MSBUILD : error : System.ObjectDisposedException: This object is no longer valid because the MetadataLoadContext that created it has been disposed. at System.Reflection.TypeLoading.RoProperty.get_Name() at Microsoft.Build.Execution.ReflectableTaskPropertyInfo..ctor(PropertyInfo propertyInfo, ...) at Microsoft.Build.Shared.LoadedType..ctor(Type type, ...) ``` ### Root Cause The `MetadataLoadContext _context` field in TypeLoader.cs (line 46) was declared as static, causing it to be shared across all threads: 1. Thread A creates a MetadataLoadContext and stores it in the static `_context` field 2. Thread B overwrites the static `_context` with its own MetadataLoadContext 3. Thread A attempts to access properties on types loaded from its (now-replaced) context 4. Thread B disposes the `_context` 5. Thread A crashes with ObjectDisposedException when accessing `propertyInfo.Name` ## Changes Made Made the MetadataLoadContext instance-local instead of static to eliminate the shared state: 1. **Removed the static `_context` field** - Eliminated the race condition source 2. **Renamed and refactored `CreateMetadataLoadContext`** - Changed from `LoadAssemblyUsingMetadataLoadContext` to return only `MetadataLoadContext` to enable idiomatic C# resource management with `using` statement. The function name now accurately reflects that it only creates the context without loading assemblies. 3. **Updated `GetLoadedTypeFromTypeNameUsingMetadataLoadContext`** - Uses `using` statement for automatic disposal instead of manual disposal in finally block. Assembly loading now happens separately after context creation. ### Why This Works The MetadataLoadContext is only used to extract metadata (property names, types, attributes) which is stored as strings/primitives during LoadedType construction. All metadata extraction completes before context disposal via the `using` statement. The actual task execution happens in the TaskHost process where assemblies are loaded normally, so no access to disposed contexts occurs during runtime. ## Testing - ✅ Full build completes successfully - ✅ All TypeLoader unit tests pass - ✅ All 59 TaskRegistry unit tests pass - ✅ Sample projects build correctly ## Notes The use of C#'s `using` statement for resource disposal follows .NET best practices and makes the code cleaner and more maintainable compared to manual disposal in a finally block. The function naming has been updated to accurately reflect its purpose (creating a context rather than loading an assembly). <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: AR-May <67507805+AR-May@users.noreply.github.com>
1 parent 7085356 commit 355d87e

File tree

1 file changed

+5
-9
lines changed

1 file changed

+5
-9
lines changed

src/Shared/TypeLoader.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ internal class TypeLoader
4343
/// </summary>
4444
private Func<Type, object, bool> _isDesiredType;
4545

46-
private static MetadataLoadContext _context;
47-
4846
private static readonly string[] runtimeAssemblies = findRuntimeAssembliesWithMicrosoftBuildFramework();
4947
private static string microsoftBuildFrameworkPath;
5048

@@ -188,7 +186,7 @@ private static Assembly LoadAssembly(AssemblyLoadInfo assemblyLoadInfo)
188186
}
189187
}
190188

191-
private static Assembly LoadAssemblyUsingMetadataLoadContext(AssemblyLoadInfo assemblyLoadInfo)
189+
private static MetadataLoadContext CreateMetadataLoadContext(AssemblyLoadInfo assemblyLoadInfo)
192190
{
193191
string path = assemblyLoadInfo.AssemblyFile;
194192
string[] localAssemblies = Directory.GetFiles(Path.GetDirectoryName(path), "*.dll");
@@ -205,8 +203,7 @@ private static Assembly LoadAssemblyUsingMetadataLoadContext(AssemblyLoadInfo as
205203
assembliesDictionary[Path.GetFileName(runtimeAssembly)] = runtimeAssembly;
206204
}
207205

208-
_context = new(new PathAssemblyResolver(assembliesDictionary.Values));
209-
return _context.LoadFromAssemblyPath(path);
206+
return new MetadataLoadContext(new PathAssemblyResolver(assembliesDictionary.Values));
210207
}
211208

212209
/// <summary>
@@ -398,7 +395,8 @@ private LoadedType GetLoadedTypeFromTypeNameUsingMetadataLoadContext(string type
398395
return _publicTypeNameToLoadedType.GetOrAdd(typeName, typeName =>
399396
{
400397
MSBuildEventSource.Log.LoadAssemblyAndFindTypeStart();
401-
Assembly loadedAssembly = LoadAssemblyUsingMetadataLoadContext(_assemblyLoadInfo);
398+
using MetadataLoadContext context = CreateMetadataLoadContext(_assemblyLoadInfo);
399+
Assembly loadedAssembly = context.LoadFromAssemblyPath(_assemblyLoadInfo.AssemblyFile);
402400
Type foundType = null;
403401
int numberOfTypesSearched = 0;
404402

@@ -437,10 +435,8 @@ private LoadedType GetLoadedTypeFromTypeNameUsingMetadataLoadContext(string type
437435
if (foundType != null)
438436
{
439437
MSBuildEventSource.Log.CreateLoadedTypeStart(loadedAssembly.FullName);
440-
var taskItemType = _context.LoadFromAssemblyPath(microsoftBuildFrameworkPath).GetType(typeof(ITaskItem).FullName);
438+
var taskItemType = context.LoadFromAssemblyPath(microsoftBuildFrameworkPath).GetType(typeof(ITaskItem).FullName);
441439
LoadedType loadedType = new(foundType, _assemblyLoadInfo, loadedAssembly, taskItemType, loadedViaMetadataLoadContext: true);
442-
_context?.Dispose();
443-
_context = null;
444440
MSBuildEventSource.Log.CreateLoadedTypeStop(loadedAssembly.FullName);
445441
return loadedType;
446442
}

0 commit comments

Comments
 (0)