Skip to content

Loaded assemblies from an AssemblyLoadContext not unloaded on AssemblyLoadContext.Unload() call #119648

@apilatosba

Description

@apilatosba

Description

Good morning. I want to reload a dll at runtime but I can't. I have two programs lets name them host and plugin. Host compiles to an executable and plugin compiles to a dll. Host loads a function from the plugin at runtime and i make changes to the plugin and recompile it. And then in the host i want to reload the plugin dll and want to use the updated function without needing the relaunch the host program. i have created a small example where this issue exist. For reference here is the code:

Host.cs:

using System;
using System.Reflection;
using System.Runtime.Loader;

namespace Host {
   public class PluginAssemblyLoadContext : AssemblyLoadContext {
      public PluginAssemblyLoadContext() : base(true) { }
   }

   public class HostState {
      public Assembly                  pluginAssembly;
      public PluginAssemblyLoadContext   loadContext;
      public Action                    pluginExecute;
   }

   public static class Host {
      public const string PLUGIN_PATH = "../plugin/bin/Debug/net9.0/plugin.dll";

      public static void Main() {
         HostState host = new HostState();
         host.loadContext = new PluginAssemblyLoadContext();
         ReloadDll(host);

         Console.WriteLine("Press E to execute the plugin, R to reload, or Q to quit.");

         for (;;) {
            var key = Console.ReadKey(true);
            if (false) {
            } else if (key.Key == ConsoleKey.E) {
               host.pluginExecute.Invoke();
            } else if (key.Key == ConsoleKey.R) {
               ReloadDll(host);
            } else if (key.Key == ConsoleKey.Q) {
               return;
            }
         }
      }

      public static void ReloadDll(HostState host) {
         host.pluginAssembly = null;
         host.pluginExecute = null;

         GC.Collect();
         GC.WaitForPendingFinalizers();

         host.loadContext?.Unload();

         GC.Collect();
         GC.WaitForPendingFinalizers();

         host.loadContext = new PluginAssemblyLoadContext();
         host.pluginAssembly = host.loadContext.LoadFromAssemblyPath(Path.GetFullPath(PLUGIN_PATH));

         Type type = host.pluginAssembly.GetType("Plugin");
         MethodInfo methodInfo = type.GetMethod("Execute", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, []);

         if (methodInfo != null) {
            host.pluginExecute = methodInfo.CreateDelegate<Action>();
         }
      }
   }
}

Plugin.cs:

using System;

public static class Plugin {
   public static void Execute() {
      Console.WriteLine("1");
   }
}

The Scenario

I launch the host program. I press E to execute plugin. I got the output "1". I go to Plugin.cs. I change Execute() function to output "2" instead of "1". I go back to the terminal where host program is running. I press R and then I press E.

What Happens

I got the output "1" again.

What should have happened

I should have got the output "2" instead.

What I have tried

  • I tried to set every reference to null which i obtained from the PluginAssemblyLoadContext as mentioned in the third remark of ms docs
  • I tried to change the assembly version when i build the plugin.dll but that didn't help either.
  • Called GC.Collect() and GC.WaitForPendingFinalizers() to make sure every reference gets garbage collected.

Why I want to do this

I have a game engine and i want to be able reload the game dll without needing to restart the editor of the engine. So in this case host would be the editor of the engine and plugin would be the game.

Specs

  • cpu: x86-64
  • os: debian sid
  • dotnet sdk version: 9.0.305
-

Any idea how to reload a dll at runtime? Any help would be appreciated.

Reproduction Steps

clone the repo and then follow the scenario above.

Expected behavior

to be able to execute the new function in recompled plugin.dll

Actual behavior

still old function is being called.

Regression?

idk

Known Workarounds

i know no workarounds

Configuration

  1. dotnet --version: 9.0.305
  2. os: debian sid
  3. cpu architecture: x86-64
  4. i dont think this problem is specific to this configuration tho i have not tried other configurations.

Other information

i think the problem is the AssemblyLoadContext.Unload() function doesn't unload the assembly thats loaded from it.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions