roslyn: Every CSharpScript.EvaluateAsync() call generates a new assembly that does not get unloaded

Using the following code:

while(true)
{
	await Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync("4 + 8");
	System.Console.WriteLine($"Loaded assemblies: {System AppDomain.CurrentDomain.GetAssemblies().Count()}");
}

Output:

Loaded assemblies: 49
Loaded assemblies: 50
Loaded assemblies: 51
Loaded assemblies: 52
Loaded assemblies: 53
Loaded assemblies: 54
...

The scripting API generates a new assembly for every call that does not get unloaded, resulting in an ever-increasing number of loaded assemblies and thus in an increasing amount of memory consumption.

There does not appear to be a way to unload the generated assemblies since they are not collectible (How to use and debug assembly unloadability in .NET Core).

How can we unload these generated assemblies?

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 20
  • Comments: 16 (8 by maintainers)

Most upvoted comments

I think this is also related to https://github.com/dotnet/roslyn/issues/42134

@tmat Is there any plans to support unloading the assembly? This really defeats the purpose of having a dynamic code execution system.

Exposing an a handle to allow the user to unload the assembly when they know they are finished with it sounds like the most logical approach.

I think another way for this is to generate overloads to Run, RunAsync, Evaluate, EvaluateAsync that runs generated scripts (or even loads them) to take in AssemblyLoadContexts that the caller has to create subclasses to on their own, and that way they can enable the ability to unload the contexts on their end if they want.

And then have Roslyn just use those passed in contexts.

But for the cases of people using Roslyn Scripting with .NET Framework I would suggest that for .NET Framework to replace AssemblyLoadContext with AppDomain since the resolver for the load contexts are .NET 5 or newer only, while the load contexts are .NET Core 3.x or newer only.

Then this can solve the problem entirely.

But then it would possibly cause another issue: the generated code would have to load the assemblies they depend on from the program’s directory of the program’s assembly itself, and then look in the GAC (Global Assembly Cache) for the other assemblies the scripts might depend on.

@tmat

I would love to contribute this functionality. However, I have some obligations I have to look after for the time being so it might take some time before I can fully dig in.

Do you have any recommendations of material to read (other than the contributing guide) that can help familiarize myself with the concepts used in the scripting api codebase?

I assume that this namespace (https://github.com/dotnet/roslyn/tree/master/src/Scripting/Core/Hosting/AssemblyLoader) is that namespace I need to be paying attention to, am I correct in that assumption?

Thanks!