diff --git a/src/Compilers/Core/CodeAnalysisTest/AnalyzerAssemblyLoaderTests.cs b/src/Compilers/Core/CodeAnalysisTest/AnalyzerAssemblyLoaderTests.cs index 201ce70c0cc7e..ced08f1c0faec 100644 --- a/src/Compilers/Core/CodeAnalysisTest/AnalyzerAssemblyLoaderTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/AnalyzerAssemblyLoaderTests.cs @@ -23,9 +23,7 @@ using Basic.Reference.Assemblies; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.UnitTests.Diagnostics; using System.Diagnostics; -using System.ComponentModel; #if NET using Roslyn.Test.Utilities.CoreClr; @@ -1653,7 +1651,7 @@ public void AssemblyLoadingInNonDefaultContext_AnalyzerReferencesSystemCollectio // Load the compiler assembly and a modified version of S.C.I into the compiler load context. We // expect the analyzer will use the bogus S.C.I in the compiler context instead of the one // in the host context. - var alc = new AssemblyLoadContext(nameof(AssemblyResolver_FirstOneWins), isCollectible: true); + var alc = new AssemblyLoadContext(nameof(AssemblyResolver_FirstOneWins), isCollectible: false); _ = alc.LoadFromAssemblyPath(TestFixture.UserSystemCollectionsImmutable); _ = alc.LoadFromAssemblyPath(typeof(AnalyzerAssemblyLoader).GetTypeInfo().Assembly.Location); var loader = kind switch @@ -1677,8 +1675,33 @@ public void AssemblyLoadingInNonDefaultContext_AnalyzerReferencesSystemCollectio Assert.Equal("42", sb.ToString()); }); + } - alc.Unload(); + [Theory] + [CombinatorialData] + public void AssemblyLoading_DoesNotUseCollectibleALCs(AnalyzerTestKind kind) + { + // This validation is critical to our VS / CLI performance. We ship several analyzers and source-generators in the + // SDK (NetAnalyzers & Razor generators) that are added to most projects. We want to ship these as Ready2Run so that + // we reduce JIT time. However, when an assembly is loaded into a collectible AssemblyLoadContext it prevents any of + // the R2R logic from being used. + + Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) => + { + loader.AddDependencyLocation(testFixture.Delta1); + loader.AddDependencyLocation(testFixture.Gamma); + + Assembly gamma = loader.LoadFromPath(testFixture.Gamma); + Assert.NotNull(gamma); + + var contexts = loader.GetDirectoryLoadContextsSnapshot(); + Assert.NotEmpty(contexts); + + foreach (var context in contexts) + { + Assert.False(context.IsCollectible, "AnalyzerAssemblyLoader should not use collectible assembly load contexts."); + } + }); } #endif diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs index 1fe98873aa0da..485d3909254b0 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerAssemblyLoader.Core.cs @@ -165,29 +165,10 @@ internal DirectoryLoadContext[] GetDirectoryLoadContextsSnapshot() private partial void DisposeWorker() { - var contexts = ArrayBuilder.GetInstance(); lock (_guard) { - foreach (var (_, context) in _loadContextByDirectory) - contexts.Add(context); - _loadContextByDirectory.Clear(); } - - foreach (var context in contexts) - { - try - { - context.Unload(); - CodeAnalysisEventSource.Log.DisposeAssemblyLoadContext(context.Directory, context.ToString()); - } - catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) - { - CodeAnalysisEventSource.Log.DisposeAssemblyLoadContextException(context.Directory, ex.ToString(), context.ToString()); - } - } - - contexts.Free(); } internal sealed class DirectoryLoadContext : AssemblyLoadContext @@ -196,7 +177,7 @@ internal sealed class DirectoryLoadContext : AssemblyLoadContext private readonly AnalyzerAssemblyLoader _loader; public DirectoryLoadContext(string directory, AnalyzerAssemblyLoader loader) - : base(isCollectible: true) + : base(isCollectible: false) { Directory = directory; _loader = loader;