-1

Working on one PoC where i have load and unload assembly on the fly and do Hot reload. i have created custom load context and loading assembly from one folder. Assembly contains only one simple class and implements interface.

public class MyPlugin : IMyPluginInterface
{
    public void DoSomething()
    {
        Console.Write("Hello From Plugin");
    } 

    public void DoSomethingElse()
    {
        Console.Write("Hello From DoSomethingElse Function");
    }
}

My host project, ConfigureServices method where i am loading this assembly.

public void ConfigureServices(IServiceCollection services) { PluginDiscoveryService pluginDiscoverService = new PluginDiscoveryService();

        var binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        string pluginPath = Path.Combine(binFolderPath, "Plugin");
        pluginPath = pluginPath + "\\MyPlugin.dll";


        WeakReference hostAlcWeakRef;
        IEnumerable<Type> types;
        pluginDiscoverService.LoadPluginAssemblyUsingDI(out hostAlcWeakRef, out types);

        foreach (var type in types)
        {
            services.AddTransient(typeof(IMyPluginInterface), type);
        }


        types = null;
        var removePluginInterface = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(IMyPluginInterface));
        services.Remove(removePluginInterface);
        removePluginInterface = null;


        for (int i = 0; hostAlcWeakRef.IsAlive && (i < 10); i++)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        Console.WriteLine($"Unload success: {!hostAlcWeakRef.IsAlive}");
    }

here hostAlcWeakRef.IsAlive is coming true. if i comment out foreach (var type in types) dont register type then i am able to unload it.

Please help

Erik Eidt
  • 23,049
  • 2
  • 29
  • 53
Khyati Desai
  • 41
  • 1
  • 6
  • Even if unloading works in ConfigureServices i foresee problems when you want to do it in the running app (That would be what i understand when you say hot reload). The thing you want to unload then might already be injected somewhere and be actually in use or someone has injected a ServiceCollection. That will be then a readonly copy if i recall correctly that will have references on your type. Proper unloading presumably needs a different kind of ServiceCollection implementation or as suggested in one answer you need another layer of abstraction between the ServiceCollection and your plugins – Ralf Jul 14 '23 at 13:20

1 Answers1

0

The unloading of assemblies loaded via custom AssemblyLoadContext (ALC) can indeed be difficult owing to the complex rules of .NET's garbage collector and the manner in which assemblies are loaded into memory.

In your case, it seems the problem emerges from the utilization of the ServiceCollection and the way you are registering your services. Once you register your services and the dependency injection container is built, a reference to your loaded assembly is being retained, thus blocking the ALC from being unloaded.

The first thing you need to confirm is that your PluginDiscoveryService is correctly implementing the Unload method, and you're not storing any references to the Assembly objects themselves.

If you're solely using the types and not storing any Assembly objects, then the problem could be that the DI container (IServiceCollection) is holding references to the loaded assembly. If you are in control of when these types are needed, you could ponder using factories that create these instances when necessary, and discard them instantly following use.

public class MyPluginFactory : IMyPluginFactory
{
    private readonly WeakReference _alcWeakRef;
    private readonly Type _pluginType;

    public MyPluginFactory(WeakReference alcWeakRef, Type pluginType)
    {
        _alcWeakRef = alcWeakRef;
        _pluginType = pluginType;
    }

    public IMyPluginInterface CreatePluginInstance()
    {
        return _alcWeakRef.IsAlive ? (IMyPluginInterface)Activator.CreateInstance(_pluginType) : null;
    }
}

Then you can register MyPluginFactory instead of registering IMyPluginInterface. In this way, you govern the lifecycle of the IMyPluginInterface instances and can guarantee they get collected by the GC, permitting your ALC to be unloaded.

Furthermore, it's important to note that just because an ALC is not unloaded immediately after you call Unload() and set up your GC to collect, doesn't mean it won't be unloaded at all. The GC works in the background and will collect when it deems necessary.

If you're relying on the immediate collection of these objects for your hot reload functionality, you may need to rethink your design or compel a GC collection with GC.Collect(), GC.WaitForPendingFinalizers(), but it's generally considered bad practice to force a collection. Be sure to exercise caution to avoid strong references from a "rooted" object graph. If your loaded assemblies are still being referenced, they will not be collected and thus not unloaded.

  • Here i am removing the reference from the service collection .. var removePluginInterface = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(IMyPluginInterface)); services.Remove(removePluginInterface); removePluginInterface = null; – Khyati Desai Jul 14 '23 at 12:08
  • Welcome to Stack Overflow, daniya shyanne! All eight of your answers here since you joined 3 days ago appear likely to be entirely or partially written by AI (e.g., ChatGPT). Please be aware that [posting AI-generated content is not allowed here](//meta.stackoverflow.com/q/421831). If you used an AI tool to assist with any answer, I would encourage you to delete it. We do hope you'll stick around and be a valuable part of our community by posting *your own* quality content. Thanks! – NotTheDr01ds Jul 16 '23 at 00:19
  • **Readers should review this answer carefully and critically, as AI-generated information often contains fundamental errors and misinformation.** If you observe quality issues and/or have reason to believe that this answer was generated by AI, please leave feedback accordingly. – NotTheDr01ds Jul 16 '23 at 00:19