Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 16, 2025

Problem

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

Solution

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. Updated LoadAssemblyUsingMetadataLoadContext - Returns (Assembly, MetadataLoadContext) tuple to pass the context to the caller
  3. Updated GetLoadedTypeFromTypeNameUsingMetadataLoadContext - Uses a local context variable and ensures disposal happens only after all metadata extraction is complete via try-finally

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. 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

Fixes #10664

Original prompt

This section details on the original issue you should resolve

<issue_title>Race condition in TaskRegistry in /mt mode</issue_title>
<issue_description>It seems there is a race condition in TaskRegistry/TypeLoader class

    "C:\Users\alinama\work\testRepos\many-projects\many-projects.sln" (rebuild target) (1) ->
       "C:\Users\alinama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.csproj" (Rebuild target) (3) ->
         MSBUILD : error : This is an unhandled exception in MSBuild -- PLEASE UPVOTE AN EXISTING ISSUE OR FILE A NEW ONE A
       T https://aka.ms/msbuild/unhandled [C:\Users\alinama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.csproj
       ]
       MSBUILD : error :     System.ObjectDisposedException: This object is no longer valid because the MetadataLoadContext
        that created it has been disposed. [C:\Users\alinama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.cspro
       j]
       MSBUILD : error :    at System.Reflection.MetadataLoadContext.DisposeCheck() [C:\Users\alinama\work\testRepos\many-p
       rojects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at System.Reflection.TypeLoading.Ecma.EcmaProperty.ComputeName() [C:\Users\alinama\work\testRep
       os\many-projects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at System.Reflection.TypeLoading.RoProperty.get_Name() [C:\Users\alinama\work\testRepos\many-pr
       ojects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at Microsoft.Build.Execution.ReflectableTaskPropertyInfo..ctor(PropertyInfo propertyInfo, Boole
       an output, Boolean required, Boolean isAssignableToITaskItemType) in C:\Users\alinama\work\msbuild\src\Build\Instanc
       e\ReflectableTaskPropertyInfo.cs:line 61 [C:\Users\alinama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.
       csproj]
       MSBUILD : error :    at Microsoft.Build.Shared.LoadedType..ctor(Type type, AssemblyLoadInfo assemblyLoadInfo, Assemb
       ly loadedAssembly, Type iTaskItemType, Boolean loadedViaMetadataLoadContext) in C:\Users\alinama\work\msbuild\src\Sh
       ared\LoadedType.cs:line 131 [C:\Users\alinama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at Microsoft.Build.Shared.TypeLoader.AssemblyInfoToLoadedTypes.<GetLoadedTypeFromTypeNameUsingM
       etadataLoadContext>b__10_0(String typeName) in C:\Users\alinama\work\msbuild\src\Shared\TypeLoader.cs:line 427 [C:\U
       sers\alinama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
        [C:\Users\alinama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at Microsoft.Build.Shared.TypeLoader.AssemblyInfoToLoadedTypes.GetLoadedTypeFromTypeNameUsingMe
       tadataLoadContext(String typeName) in C:\Users\alinama\work\msbuild\src\Shared\TypeLoader.cs:line 384 [C:\Users\alin
       ama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at Microsoft.Build.Shared.TypeLoader.AssemblyInfoToLoadedTypes.GetLoadedTypeByTypeName(String t
       ypeName, Boolean useTaskHost) in C:\Users\alinama\work\msbuild\src\Shared\TypeLoader.cs:line 330 [C:\Users\alinama\w
       ork\testRepos\many-projects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at Microsoft.Build.Shared.TypeLoader.GetLoadedType(ConcurrentDictionary`2 cache, String typeNam
       e, AssemblyLoadInfo assembly, Boolean useTaskHost) in C:\Users\alinama\work\msbuild\src\Shared\TypeLoader.cs:line 25
       4 [C:\Users\alinama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at Microsoft.Build.Shared.TypeLoader.Load(String typeName, AssemblyLoadInfo assembly, Boolean u
       seTaskHost) in C:\Users\alinama\work\msbuild\src\Shared\TypeLoader.cs:line 222 [C:\Users\alinama\work\testRepos\many
       -projects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at Microsoft.Build.BackEnd.AssemblyTaskFactory.InitializeFactory(AssemblyLoadInfo loadInfo, Str
       ing taskName, IDictionary`2 taskParameters, String taskElementContents, IDictionary`2 taskFactoryIdentityParameters,
        Boolean taskHostExplicitlyRequested, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation, St
       ring taskProjectFile) in C:\Users\alinama\work\msbuild\src\Build\Instance\TaskFactories\AssemblyTaskFactory.cs:line
       296 [C:\Users\alinama\work\testRepos\many-projects\ClassLibrary2\ClassLibrary2.csproj]
       MSBUILD : error :    at Microsoft.Build.Execution.TaskRegistry.RegisteredTaskRecord.GetTaskFactory(TargetLoggingCont
       ext targetLoggingContext, ElementLocation elementLocation, String taskProjectFile, Boolean isMultiThreadedBuild) in
       C:\Users\alinama\work\msbuild\src\Build\Instance\TaskRegistry.cs:line 1515 [C:\U...

</details>

Fixes dotnet/msbuild#12645

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for you](https://github.com/dotnet/msbuild/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.

@dotnet-policy-service
Copy link
Contributor

Hello @@copilot, I noticed that you’re changing an .swr file or any file under src/Package/MSBuild.VSSetup.. Please make sure to validate this change by an experimental VS insertion. This is accomplished by pushing to an exp/* branch, which requires write permissions to this repo.

The race condition occurred when multiple threads were loading types using MetadataLoadContext in /mt mode:
- Thread A would create a MetadataLoadContext and store it in the static _context field
- Thread B would overwrite the static _context with its own MetadataLoadContext
- Thread A would try to access properties on types loaded from the now-replaced context
- Thread B would dispose the _context, causing ObjectDisposedException in Thread A

The fix:
- Removed the static _context field that was being shared across threads
- Made LoadAssemblyUsingMetadataLoadContext return both the assembly and context as a tuple
- Changed GetLoadedTypeFromTypeNameUsingMetadataLoadContext to use a local context variable
- Wrapped all reflection operations in a try-finally to ensure proper disposal of the context
- The context is now disposed only after all reflection operations on the loaded types are complete

Co-authored-by: AR-May <[email protected]>
Copilot AI changed the title [WIP] Fix race condition in TaskRegistry in /mt mode Fix race condition in TaskRegistry/TypeLoader when building with /mt /m mode Oct 16, 2025
Copilot finished work on behalf of AR-May October 16, 2025 15:06
Copilot AI requested a review from AR-May October 16, 2025 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Can't instantiate an unnamed object if it's constructor takes exactly one parameter?

2 participants