Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,29 +165,10 @@ internal DirectoryLoadContext[] GetDirectoryLoadContextsSnapshot()

private partial void DisposeWorker()
{
var contexts = ArrayBuilder<DirectoryLoadContext>.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());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I waffled on whether to remove these EventSource methods. I see that they are numbered and didn't want to throw off any telemetry. Would marking them as obsolete be the way to go?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a reasonable approach.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do this in a follow up.

}
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
Expand All @@ -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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CyrusNajmabadi I saw that Extensions use the assembly loader. Will this break those scenarios?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extension assembly support is supposed to be able to hot unload extensions. We definitely are calling it here here -

public void Unload() => assemblyLoader.Dispose();

In this, are the assemblies still getting unloaded, but we're not disposing of the ALC? Or are both the assemblies and ALC staying around?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to take this as is but will follow up with a PR making isCollectible configurable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah. we should make this configurable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #79997

{
Directory = directory;
_loader = loader;
Expand Down
Loading