From 825e40751538a9c94605980ba438a468f3834f82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:47:18 +0000 Subject: [PATCH 1/2] Initial plan From 6800098f47213b490a9d35a5086de79672710273 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:57:53 +0000 Subject: [PATCH 2/2] Fix race condition in TypeLoader with MetadataLoadContext 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 <67507805+AR-May@users.noreply.github.com> --- src/Shared/TypeLoader.cs | 83 +++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/src/Shared/TypeLoader.cs b/src/Shared/TypeLoader.cs index a4bd6f6ec28..f8eff394361 100644 --- a/src/Shared/TypeLoader.cs +++ b/src/Shared/TypeLoader.cs @@ -43,8 +43,6 @@ internal class TypeLoader /// private Func _isDesiredType; - private static MetadataLoadContext _context; - private static readonly string[] runtimeAssemblies = findRuntimeAssembliesWithMicrosoftBuildFramework(); private static string microsoftBuildFrameworkPath; @@ -188,7 +186,7 @@ private static Assembly LoadAssembly(AssemblyLoadInfo assemblyLoadInfo) } } - private static Assembly LoadAssemblyUsingMetadataLoadContext(AssemblyLoadInfo assemblyLoadInfo) + private static (Assembly assembly, MetadataLoadContext context) LoadAssemblyUsingMetadataLoadContext(AssemblyLoadInfo assemblyLoadInfo) { string path = assemblyLoadInfo.AssemblyFile; string[] localAssemblies = Directory.GetFiles(Path.GetDirectoryName(path), "*.dll"); @@ -205,8 +203,9 @@ private static Assembly LoadAssemblyUsingMetadataLoadContext(AssemblyLoadInfo as assembliesDictionary[Path.GetFileName(runtimeAssembly)] = runtimeAssembly; } - _context = new(new PathAssemblyResolver(assembliesDictionary.Values)); - return _context.LoadFromAssemblyPath(path); + MetadataLoadContext context = new(new PathAssemblyResolver(assembliesDictionary.Values)); + Assembly assembly = context.LoadFromAssemblyPath(path); + return (assembly, context); } /// @@ -384,56 +383,62 @@ private LoadedType GetLoadedTypeFromTypeNameUsingMetadataLoadContext(string type return _publicTypeNameToLoadedType.GetOrAdd(typeName, typeName => { MSBuildEventSource.Log.LoadAssemblyAndFindTypeStart(); - Assembly loadedAssembly = LoadAssemblyUsingMetadataLoadContext(_assemblyLoadInfo); + (Assembly loadedAssembly, MetadataLoadContext context) = LoadAssemblyUsingMetadataLoadContext(_assemblyLoadInfo); Type foundType = null; int numberOfTypesSearched = 0; - // Try direct type lookup first (fastest) - if (!string.IsNullOrEmpty(typeName)) + try { - foundType = loadedAssembly.GetType(typeName, throwOnError: false); - if (foundType != null && foundType.IsPublic && _isDesiredType(foundType, null)) + // Try direct type lookup first (fastest) + if (!string.IsNullOrEmpty(typeName)) { - numberOfTypesSearched = 1; + foundType = loadedAssembly.GetType(typeName, throwOnError: false); + if (foundType != null && foundType.IsPublic && _isDesiredType(foundType, null)) + { + numberOfTypesSearched = 1; + } } - } - // Fallback: enumerate all types for partial matching - if (foundType == null) - { - foreach (Type publicType in loadedAssembly.GetExportedTypes()) + // Fallback: enumerate all types for partial matching + if (foundType == null) { - numberOfTypesSearched++; - try + foreach (Type publicType in loadedAssembly.GetExportedTypes()) { - if (_isDesiredType(publicType, null) && (typeName.Length == 0 || TypeLoader.IsPartialTypeNameMatch(publicType.FullName, typeName))) + numberOfTypesSearched++; + try { - foundType = publicType; - break; + if (_isDesiredType(publicType, null) && (typeName.Length == 0 || TypeLoader.IsPartialTypeNameMatch(publicType.FullName, typeName))) + { + foundType = publicType; + break; + } + } + catch + { + // Ignore types that can't be loaded/reflected upon. + // These types might be needed out of proc and be resolved there. } - } - catch - { - // Ignore types that can't be loaded/reflected upon. - // These types might be needed out of proc and be resolved there. } } - } - if (foundType != null) - { - MSBuildEventSource.Log.CreateLoadedTypeStart(loadedAssembly.FullName); - var taskItemType = _context.LoadFromAssemblyPath(microsoftBuildFrameworkPath).GetType(typeof(ITaskItem).FullName); - LoadedType loadedType = new(foundType, _assemblyLoadInfo, loadedAssembly, taskItemType, loadedViaMetadataLoadContext: true); - _context?.Dispose(); - _context = null; - MSBuildEventSource.Log.CreateLoadedTypeStop(loadedAssembly.FullName); - return loadedType; - } + if (foundType != null) + { + MSBuildEventSource.Log.CreateLoadedTypeStart(loadedAssembly.FullName); + var taskItemType = context.LoadFromAssemblyPath(microsoftBuildFrameworkPath).GetType(typeof(ITaskItem).FullName); + LoadedType loadedType = new(foundType, _assemblyLoadInfo, loadedAssembly, taskItemType, loadedViaMetadataLoadContext: true); + MSBuildEventSource.Log.CreateLoadedTypeStop(loadedAssembly.FullName); + return loadedType; + } - MSBuildEventSource.Log.LoadAssemblyAndFindTypeStop(_assemblyLoadInfo.AssemblyFile, numberOfTypesSearched); + MSBuildEventSource.Log.LoadAssemblyAndFindTypeStop(_assemblyLoadInfo.AssemblyFile, numberOfTypesSearched); - return null; + return null; + } + finally + { + // Dispose the context after all reflection operations are complete + context?.Dispose(); + } }); }