/*
* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Runtime
* File: RuntimePluginLoader.cs
*
* RuntimePluginLoader.cs is part of VNLib.Plugins.Runtime which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Runtime is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*
* VNLib.Plugins.Runtime is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VNLib.Plugins.Runtime. If not, see http://www.gnu.org/licenses/.
*/
using System;
using System.IO;
using System.Reflection;
using VNLib.Utils;
using VNLib.Utils.IO;
using VNLib.Utils.Logging;
namespace VNLib.Plugins.Runtime
{
///
/// A runtime .NET assembly loader specialized to load
/// assemblies that export types.
///
public sealed class RuntimePluginLoader : VnDisposeable, IPluginReloadEventHandler
{
private readonly IPluginAssemblyLoader Loader;
private readonly ILogProvider? Log;
private readonly IDisposable? Watcher;
///
/// Gets the plugin assembly loader configuration information
///
public IPluginAssemblyLoadConfig Config => Loader.Config;
///
/// Gets the plugin lifecycle controller
///
public PluginController Controller { get; }
///
/// Creates a new with the specified config and host config dom.
///
/// The plugin's assembly loader
/// A log provider to write plugin unload log events to
///
public RuntimePluginLoader(IPluginAssemblyLoader loader, ILogProvider? log)
{
ArgumentNullException.ThrowIfNull(loader);
Log = log;
Loader = loader;
//Configure watcher if requested
if (loader.Config.WatchForReload)
{
Watcher = AssemblyWatcher.WatchAssembly(this, loader);
}
//Init container
Controller = new();
}
///
/// Initializes the plugin loader, and populates the
/// with initialized plugins.
///
/// A task that represents the initialization
///
///
public void InitializeController()
{
//Prep the assembly loader
Loader.Load();
//Load the main assembly
Assembly PluginAsm = Loader.GetAssembly();
//Init container from the assembly
Controller.InitializePlugins(PluginAsm);
string[] cliArgs = Environment.GetCommandLineArgs();
//Write the config to binary to pass it to the plugin
using VnMemoryStream vms = new();
//Read config data
Loader.Config.ReadConfigurationData(vms);
//Reset memstream
vms.Seek(0, SeekOrigin.Begin);
//Configure log/doms
Controller.ConfigurePlugins(vms, cliArgs);
}
///
/// Loads all configured plugins by calling
/// event hook on the current thread. Loading exceptions are aggregated so not
/// to block individual loading.
///
///
public void LoadPlugins() => Controller.LoadPlugins();
///
/// Manually reload the internal
/// which will reload the assembly and re-initialize the controller
///
/// A value that indicates if the current unload should cause a manual garbage collection
///
///
public void ReloadPlugins(bool forceGc)
{
//Not unloadable
if (!Config.Unloadable)
{
throw new NotSupportedException("The loading context is not unloadable, you may not dynamically reload plugins");
}
//All plugins must be unloaded first
UnloadAll(forceGc);
//Reload the assembly and
InitializeController();
//Load plugins
LoadPlugins();
}
///
/// Calls the method for all plugins within the lifecycle controller
/// and invokes the
/// for all listeners.
///
///
public void UnloadPlugins() => Controller.UnloadPlugins();
///
/// Attempts to unload all plugins within the lifecycle controller, all event handlers
/// then attempts to unload the if dynamic unloading
/// is enabled, otherwise does nothing.
///
/// A value that indicates if the current unload should cause a manual garbage collection
///
public void UnloadAll(bool forceGc)
{
UnloadPlugins();
//If the assembly loader is unloadable calls its unload method
if (Config.Unloadable)
{
Loader.Unload();
}
//Optionally wait for GC to finish
if (forceGc)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
//Process unload events
void IPluginReloadEventHandler.OnPluginUnloaded(IPluginAssemblyLoader loader)
{
try
{
//All plugins must be unloaded before the assembly loader
UnloadPlugins();
//Unload the loader before initializing
loader.Unload();
//Reload the assembly and controller
InitializeController();
//Load plugins
LoadPlugins();
}
catch (Exception ex)
{
Log?.Error("Failed reload plugins for {loader}\n{ex}", Config.AssemblyFile, ex);
}
}
///
protected override void Free()
{
//Cleanup
Watcher?.Dispose();
Controller.Dispose();
Loader.Dispose();
}
}
}