From 5ddef0fcb742e77b99a0e17015d2eea0a1d4131a Mon Sep 17 00:00:00 2001 From: vnugent Date: Thu, 9 Mar 2023 01:48:28 -0500 Subject: Omega cache, session, and account provider complete overhaul --- lib/Plugins.Runtime/src/IPluginEventListener.cs | 49 +++++ lib/Plugins.Runtime/src/IPluginEventRegistrar.cs | 47 +++++ lib/Plugins.Runtime/src/ITypedPluginConsumer.cs | 47 +++++ lib/Plugins.Runtime/src/LivePlugin.cs | 66 ++++--- lib/Plugins.Runtime/src/LoaderExtensions.cs | 174 +++++++++++------- lib/Plugins.Runtime/src/PluginController.cs | 127 +++++++++++++ lib/Plugins.Runtime/src/PluginEventRegistration.cs | 69 +++++++ lib/Plugins.Runtime/src/RuntimePluginLoader.cs | 201 +++++++++------------ .../src/VNLib.Plugins.Runtime.csproj | 3 +- 9 files changed, 571 insertions(+), 212 deletions(-) create mode 100644 lib/Plugins.Runtime/src/IPluginEventListener.cs create mode 100644 lib/Plugins.Runtime/src/IPluginEventRegistrar.cs create mode 100644 lib/Plugins.Runtime/src/ITypedPluginConsumer.cs create mode 100644 lib/Plugins.Runtime/src/PluginController.cs create mode 100644 lib/Plugins.Runtime/src/PluginEventRegistration.cs (limited to 'lib/Plugins.Runtime/src') diff --git a/lib/Plugins.Runtime/src/IPluginEventListener.cs b/lib/Plugins.Runtime/src/IPluginEventListener.cs new file mode 100644 index 0000000..5d97343 --- /dev/null +++ b/lib/Plugins.Runtime/src/IPluginEventListener.cs @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: IPluginEventListener.cs +* +* IPluginEventListener.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/. +*/ + +namespace VNLib.Plugins.Runtime +{ + /// + /// Represents a plugin event consumer. + /// + public interface IPluginEventListener + { + /// + /// Called by the registered + /// to notify this listener that the plugins within the collection + /// have been initialized and loaded + /// + /// The collection on which the load event occured + /// The registration state parameter + void OnPluginLoaded(PluginController controller, object? state); + /// + /// Called by the registered + /// to notify this listener that this plugins within the + /// collection have been unloaded + /// + /// The controller that is reloading + /// The registration state parameter + void OnPluginUnloaded(PluginController controller, object? state); + } +} diff --git a/lib/Plugins.Runtime/src/IPluginEventRegistrar.cs b/lib/Plugins.Runtime/src/IPluginEventRegistrar.cs new file mode 100644 index 0000000..120f437 --- /dev/null +++ b/lib/Plugins.Runtime/src/IPluginEventRegistrar.cs @@ -0,0 +1,47 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: IPluginEventRegistrar.cs +* +* IPluginEventRegistrar.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/. +*/ + +namespace VNLib.Plugins.Runtime +{ + /// + /// Represents a type that accepts + /// event handlers and allow them to unload events + /// + public interface IPluginEventRegistrar + { + /// + /// Registers a plugin event listener + /// + /// The event handler instance to register + /// An optional state paremeter to pass to the event handler + void Register(IPluginEventListener listener, object? state = null); + + /// + /// Unregisters the event listener + /// + /// The event handler instance to unregister + /// A value that indicates if the event handler was successfully unregistered + bool Unregister(IPluginEventListener listener); + } +} diff --git a/lib/Plugins.Runtime/src/ITypedPluginConsumer.cs b/lib/Plugins.Runtime/src/ITypedPluginConsumer.cs new file mode 100644 index 0000000..9c8a477 --- /dev/null +++ b/lib/Plugins.Runtime/src/ITypedPluginConsumer.cs @@ -0,0 +1,47 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: ITypedPluginConsumer.cs +* +* ITypedPluginConsumer.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/. +*/ + +namespace VNLib.Plugins.Runtime +{ + /// + /// An abstraction that represents a consumer of a dynamically loaded type. + /// + /// The service type to consume + public interface ITypedPluginConsumer + { + /// + /// Invoked when the instance of the desired type is loaded. + /// This is a new instance of the desired type + /// + /// A new instance of the requested type + void OnLoad(T plugin, object? state); + + /// + /// Called when the loader that maintains the instance is unloading + /// the type. + /// + /// The instance of the type that is being unloaded + void OnUnload(T plugin, object? state); + } +} diff --git a/lib/Plugins.Runtime/src/LivePlugin.cs b/lib/Plugins.Runtime/src/LivePlugin.cs index c0011dd..ae4c90b 100644 --- a/lib/Plugins.Runtime/src/LivePlugin.cs +++ b/lib/Plugins.Runtime/src/LivePlugin.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime @@ -27,12 +27,12 @@ using System.Linq; using System.Reflection; using System.Text.Json; -using VNLib.Utils.Logging; using VNLib.Utils.Extensions; using VNLib.Plugins.Attributes; namespace VNLib.Plugins.Runtime { + /// /// /// Wrapper for a loaded instance, used internally @@ -46,6 +46,8 @@ namespace VNLib.Plugins.Runtime /// public class LivePlugin : IEquatable, IEquatable { + private bool _loaded; + /// /// The plugin's property during load time /// @@ -57,14 +59,24 @@ namespace VNLib.Plugins.Runtime /// by he current instance /// public IPlugin? Plugin { get; private set; } + + /// + /// The assembly that this plugin was created from + /// + public Assembly OriginAsm { get; } - private readonly Type PluginType; + /// + /// The exposed runtime type of the plugin. Equivalent to + /// calling Plugin.GetType() + /// + public Type PluginType { get; } private ConsoleEventHandlerSignature? PluginConsoleHandler; - internal LivePlugin(IPlugin plugin) + internal LivePlugin(IPlugin plugin, Assembly originAsm) { Plugin = plugin; + OriginAsm = originAsm; PluginType = plugin.GetType(); GetConsoleHandler(); } @@ -153,42 +165,38 @@ namespace VNLib.Plugins.Runtime /// /// Calls the method on the plugin if its loaded /// - internal void LoadPlugin() => Plugin?.Load(); + internal void LoadPlugin() + { + //Load and set loaded flag + Plugin?.Load(); + _loaded = true; + } /// - /// Unloads all loaded endpoints from - /// that they were loaded to, then unloads the plugin. + /// Unloads the plugin, only if the plugin was successfully loaded by + /// calling the event hook. /// - /// An optional log provider to write unload exceptions to - /// - /// If is no null unload exceptions are swallowed and written to the log - /// - internal void UnloadPlugin(ILogProvider? logSink) + internal void UnloadPlugin() { - /* - * We need to swallow plugin unload errors to avoid - * unknown state, making sure endpoints are properly - * unloaded! - */ + //Remove delegate handler to the plugin to remove refs + PluginConsoleHandler = null; + + //Only call unload if the plugin successfully loaded + if (!_loaded) + { + return; + } + try { - //Unload the plugin Plugin?.Unload(); } - catch (Exception ex) + finally { - //Create an unload wrapper for the exception - PluginUnloadException wrapper = new("Exception raised during plugin unload", ex); - if (logSink == null) - { - throw wrapper; - } - //Write error to log sink - logSink.Error(wrapper); + Plugin = null; } - Plugin = null; - PluginConsoleHandler = null; } + /// public override bool Equals(object? obj) { diff --git a/lib/Plugins.Runtime/src/LoaderExtensions.cs b/lib/Plugins.Runtime/src/LoaderExtensions.cs index 795dcf5..13fbb11 100644 --- a/lib/Plugins.Runtime/src/LoaderExtensions.cs +++ b/lib/Plugins.Runtime/src/LoaderExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime @@ -23,98 +23,148 @@ */ using System; -using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + namespace VNLib.Plugins.Runtime { + /// + /// Contains extension methods for PluginLoader library + /// public static class LoaderExtensions { - /// - /// Searches all plugins within the current loader for a - /// single plugin that derrives the specified type - /// - /// The type the plugin must derrive from - /// - /// The instance of the plugin that derrives from the specified type - public static LivePlugin? GetExposedPlugin(this RuntimePluginLoader loader) + /* + * Class that manages a collection registration for a specific type + * dependency, and redirects the event calls for the consumed service + */ + private sealed class TypedRegistration : IPluginEventListener where T: class { - return loader.LivePlugins - .Where(static pl => typeof(T).IsAssignableFrom(pl.Plugin!.GetType())) - .SingleOrDefault(); + private readonly ITypedPluginConsumer _consumerEvents; + private readonly object? _userState; + + private T? _service; + private readonly Type _type; + + public TypedRegistration(ITypedPluginConsumer consumerEvents, Type type) + { + _consumerEvents = consumerEvents; + _type = type; + } + + + public void OnPluginLoaded(PluginController controller, object? state) + { + //Get the service from the loaded plugins + T service = controller.Plugins + .Where(pl => _type.IsAssignableFrom(pl.PluginType)) + .Select(static pl => (T)pl.Plugin!) + .First(); + + //Call load with the exported type + _consumerEvents.OnLoad(service, _userState); + + //Store for unload + _service = service; + } + + public void OnPluginUnloaded(PluginController controller, object? state) + { + //Unload + _consumerEvents.OnUnload(_service!, _userState); + _service = null; + } } /// - /// Searches all plugins within the current loader for a - /// single plugin that derrives the specified type + /// Registers a plugin even handler for the current + /// for a specific type. /// - /// The type the plugin must derrive from - /// - /// The instance of your custom type casted, or null if not found or could not be casted - public static T? GetExposedTypeFromPlugin(this RuntimePluginLoader loader) where T: class + /// + /// + /// The typed plugin instance event consumer + /// A handle that manages this event registration + /// + public static PluginEventRegistration RegisterForType(this PluginController collection, ITypedPluginConsumer consumer) where T: class { - LivePlugin? plugin = loader.LivePlugins - .Where(static pl => typeof(T).IsAssignableFrom(pl.Plugin!.GetType())) - .SingleOrDefault(); + Type serviceType = typeof(T); - return plugin?.Plugin as T; + //Confim the type is exposed by this collection + if(!ExposesType(collection, serviceType)) + { + throw new ArgumentException("The requested type is not exposed in this assembly"); + } + + //Create new typed listener + TypedRegistration reg = new(consumer, serviceType); + + //register event handler + return Register(collection, reg, null); } /// - /// Registers a listener delegate method to invoke when the - /// current is reloaded, and passes - /// the new instance of the specified type + /// Registers a handler to listen for plugin load/unload events /// - /// The single plugin type to register a listener for - /// - /// The delegate method to invoke when the loader has reloaded plugins /// - public static bool RegisterListenerForSingle(this RuntimePluginLoader loader, Action reloaded) where T: class + /// A handle that will unregister the listener when disposed + public static PluginEventRegistration Register(this IPluginEventRegistrar reg, IPluginEventListener listener, object? state = null) { - _ = reloaded ?? throw new ArgumentNullException(nameof(reloaded)); + reg.Register(listener, state); + return new(reg, listener); + } - //try to get the casted type from the loader - T? current = loader.GetExposedTypeFromPlugin(); + /// + /// Loads the configuration file into its format + /// for reading. + /// + /// + /// A new of the loaded configuration file + public static async Task GetPluginConfigAsync(this RuntimePluginLoader loader) + { + //Open and read the config file + await using FileStream confStream = File.OpenRead(loader.PluginConfigPath); - if (current == null) - { - return false; - } - else + JsonDocumentOptions jdo = new() { - loader.Reloaded += delegate (object? sender, EventArgs args) - { - RuntimePluginLoader wpl = (sender as RuntimePluginLoader)!; - //Get the new loaded type - T newT = (wpl.GetExposedPlugin()!.Plugin as T)!; - //Invoke reloaded action - reloaded(current, newT); - //update the new current instance - current = newT; - }; - - return true; - } + AllowTrailingCommas = true, + CommentHandling = JsonCommentHandling.Skip, + }; + + //parse the plugin config file + return await JsonDocument.ParseAsync(confStream, jdo); } /// - /// Gets all endpoints exposed by all exported plugin instances - /// within the current loader + /// Determines if the current + /// exposes the desired type on is + /// type. /// - /// - /// An enumeration of all endpoints - public static IEnumerable GetEndpoints(this RuntimePluginLoader loader) => loader.LivePlugins.SelectMany(static pl => pl.Plugin!.GetEndpoints()); + /// + /// The desired type to request + /// True if the plugin exposes the desired type, false otherwise + public static bool ExposesType(this PluginController collection, Type type) + { + return collection.Plugins + .Where(pl => type.IsAssignableFrom(pl.PluginType)) + .Any(); + } /// - /// Determines if any loaded plugin types exposes an instance of the - /// specified type + /// Searches all plugins within the current loader for a + /// single plugin that derrives the specified type /// - /// + /// The type the plugin must derrive from /// - /// True if any plugin instance exposes a the specified type, false otherwise - public static bool ExposesType(this RuntimePluginLoader loader) where T : class + /// The instance of your custom type casted, or null if not found or could not be casted + public static T? GetExposedTypes(this PluginController collection) where T: class { - return loader.LivePlugins.Any(static pl => typeof(T).IsAssignableFrom(pl.Plugin?.GetType())); + LivePlugin? plugin = collection.Plugins + .Where(static pl => typeof(T).IsAssignableFrom(pl.PluginType)) + .SingleOrDefault(); + + return plugin?.Plugin as T; } } } diff --git a/lib/Plugins.Runtime/src/PluginController.cs b/lib/Plugins.Runtime/src/PluginController.cs new file mode 100644 index 0000000..14ea7f0 --- /dev/null +++ b/lib/Plugins.Runtime/src/PluginController.cs @@ -0,0 +1,127 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: PluginController.cs +* +* PluginController.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.Linq; +using System.Text.Json; +using System.Reflection; +using System.Collections.Generic; + +using VNLib.Utils.Extensions; + +namespace VNLib.Plugins.Runtime +{ + /// + /// Manages the lifetime of a collection of instances, + /// and their dependent event listeners + /// + public sealed class PluginController : IPluginEventRegistrar + { + private readonly List _plugins; + private readonly List> _listeners; + + internal PluginController() + { + _plugins = new (); + _listeners = new (); + } + + /// + /// The current collection of plugins. Valid before the unload event. + /// + public IEnumerable Plugins => _plugins; + + /// + /// + public void Register(IPluginEventListener listener, object? state = null) + { + _ = listener ?? throw new ArgumentNullException(nameof(listener)); + + _listeners.Add(new(listener, state)); + } + + /// + public bool Unregister(IPluginEventListener listener) + { + //Remove listener + return _listeners.RemoveAll(p => p.Key == listener) > 0; + } + + + internal void InitializePlugins(Assembly asm) + { + //get all Iplugin types + Type[] types = asm.GetTypes().Where(static type => !type.IsAbstract && typeof(IPlugin).IsAssignableFrom(type)).ToArray(); + + //Initialize the new plugin instances + IPlugin[] plugins = types.Select(static t => (IPlugin)Activator.CreateInstance(t)!).ToArray(); + + //Crate new containers + LivePlugin[] lps = plugins.Select(p => new LivePlugin(p, asm)).ToArray(); + + //Store containers + _plugins.AddRange(lps); + } + + internal void ConfigurePlugins(JsonDocument hostDom, JsonDocument pluginDom, string[] cliArgs) + { + _plugins.TryForeach(lp => lp.InitConfig(hostDom, pluginDom)); + _plugins.TryForeach(lp => lp.InitLog(cliArgs)); + } + + internal void LoadPlugins() + { + //Load all plugins + _plugins.TryForeach(static p => p.LoadPlugin()); + + //Notify event handlers + _listeners.TryForeach(l => l.Key.OnPluginLoaded(this, l.Value)); + } + + internal void UnloadPlugins() + { + try + { + //Notify event handlers + _listeners.TryForeach(l => l.Key.OnPluginUnloaded(this, l.Value)); + + //Unload plugin instances + _plugins.TryForeach(static p => p.UnloadPlugin()); + } + finally + { + //Always + _plugins.Clear(); + } + } + + internal void Dispose() + { + _plugins.Clear(); + _listeners.Clear(); + } + + + } +} diff --git a/lib/Plugins.Runtime/src/PluginEventRegistration.cs b/lib/Plugins.Runtime/src/PluginEventRegistration.cs new file mode 100644 index 0000000..37d6162 --- /dev/null +++ b/lib/Plugins.Runtime/src/PluginEventRegistration.cs @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: PluginEventRegistration.cs +* +* PluginEventRegistration.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; + +namespace VNLib.Plugins.Runtime +{ + /// + /// Holds a registration for events to a given . + /// The event listener is unregistered from events when this registration is disposed. + /// + public readonly record struct PluginEventRegistration : IDisposable + { + private readonly IPluginEventRegistrar _registrar; + private readonly IPluginEventListener _listener; + + internal PluginEventRegistration(IPluginEventRegistrar container, IPluginEventListener listener) + { + _listener = listener; + _registrar = container; + } + + /// + /// Unreigsers the listner and releases held resources + /// + public readonly void Dispose() + { + _ = _registrar?.Unregister(_listener); + } + + /// + /// Unregisters a previously registered + /// from the it was registered to + /// + /// + public readonly void Unregister() + { + if (_registrar == null) + { + return; + } + if (!_registrar.Unregister(_listener)) + { + throw new InvalidOperationException("The listner has already been unregistered"); + } + } + } +} diff --git a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs index c688f8b..d79def3 100644 --- a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs +++ b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs @@ -1,11 +1,11 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime -* File: DynamicPluginLoader.cs +* File: RuntimePluginLoader.cs * -* DynamicPluginLoader.cs is part of VNLib.Plugins.Runtime which is part of the larger +* 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 @@ -24,20 +24,16 @@ using System; using System.IO; -using System.Linq; using System.Text.Json; using System.Reflection; using System.Runtime.Loader; using System.Threading.Tasks; -using System.Collections.Generic; using McMaster.NETCore.Plugins; using VNLib.Utils; using VNLib.Utils.IO; using VNLib.Utils.Logging; -using VNLib.Utils.Extensions; - namespace VNLib.Plugins.Runtime { @@ -45,50 +41,23 @@ namespace VNLib.Plugins.Runtime /// A runtime .NET assembly loader specialized to load /// assemblies that export types. /// - public class RuntimePluginLoader : VnDisposeable + public sealed class RuntimePluginLoader : VnDisposeable { - protected readonly PluginLoader Loader; - protected readonly string PluginPath; - protected readonly JsonDocument HostConfig; - protected readonly ILogProvider? Log; - protected readonly LinkedList LoadedPlugins; - - /// - /// A readonly collection of all loaded plugin wrappers - /// - public IReadOnlyCollection LivePlugins => LoadedPlugins; - - /// - /// An event that is raised before the loader - /// unloads all plugin instances - /// - protected event EventHandler? OnBeforeReloaded; - /// - /// An event that is raised after a successfull reload of all new - /// plugins for the instance - /// - protected event EventHandler? OnAfterReloaded; - - /// - /// Raised when the current loader has reloaded the assembly and - /// all plugins were successfully loaded. - /// - public event EventHandler? Reloaded; + private readonly PluginLoader Loader; + private readonly string PluginPath; + private readonly JsonDocument HostConfig; + private readonly ILogProvider? Log; /// - /// The current plugin's JSON configuration DOM loaded from the plugin's directory - /// if it exists. Only valid after first initalization + /// Gets the plugin lifetime manager. /// - public JsonDocument? PluginConfigDOM { get; private set; } - /// - /// Optional loader arguments object for the plugin - /// - protected JsonElement? LoaderArgs { get; private set; } + public PluginController Controller { get; } /// /// The path of the plugin's configuration file. (Default = pluginPath.json) /// - public string PluginConfigPath { get; init; } + public string PluginConfigPath { get; } + /// /// Creates a new with the specified /// assembly location and host config. @@ -98,18 +67,16 @@ namespace VNLib.Plugins.Runtime /// The configuration DOM to merge with plugin config DOM and pass to enabled plugins /// A value that specifies if the assembly can be unloaded /// A value that spcifies if the loader will listen for changes to the assembly file and reload the plugins - /// A value that specifies if assembly dependencies are loaded on-demand /// /// The argument may be null if is false /// /// - public RuntimePluginLoader(string pluginPath, JsonDocument? hostConfig = null, ILogProvider? log = null, bool unloadable = false, bool hotReload = false, bool lazy = false) + public RuntimePluginLoader(string pluginPath, JsonDocument? hostConfig = null, ILogProvider? log = null, bool unloadable = false, bool hotReload = false) :this( new PluginConfig(pluginPath) { IsUnloadable = unloadable || hotReload, EnableHotReload = hotReload, - IsLazyLoaded = lazy, ReloadDelay = TimeSpan.FromSeconds(1), PreferSharedTypes = true, DefaultContext = AssemblyLoadContext.Default @@ -117,6 +84,7 @@ namespace VNLib.Plugins.Runtime hostConfig, log) { } + /// /// Creates a new with the specified config and host config dom. /// @@ -126,124 +94,119 @@ namespace VNLib.Plugins.Runtime /// public RuntimePluginLoader(PluginConfig config, JsonDocument? hostConfig, ILogProvider? log) { - //Add the assembly from which the IPlugin library was loaded from - config.SharedAssemblies.Add(typeof(IPlugin).Assembly.GetName()); - + //Shared types is required so the default load context shares types + config.PreferSharedTypes = true; + //Default to empty config if null HostConfig = hostConfig ?? JsonDocument.Parse("{}"); + Loader = new(config); PluginPath = config.MainAssemblyPath; Log = log; - Loader.Reloaded += Loader_Reloaded; + + //Only regiser reload handler if the load context is unloadable + if (config.IsUnloadable) + { + //Init reloaded event handler + Loader.Reloaded += Loader_Reloaded; + } + //Set the config path default PluginConfigPath = Path.ChangeExtension(PluginPath, ".json"); - LoadedPlugins = new(); + + //Init container + Controller = new(); } private async void Loader_Reloaded(object sender, PluginReloadedEventArgs eventArgs) { try { - //Invoke reloaded events - OnBeforeReloaded?.Invoke(this, eventArgs); - //Unload all endpoints - LoadedPlugins.TryForeach(lp => lp.UnloadPlugin(Log)); - //Clear list of loaded plugins - LoadedPlugins.Clear(); - //Unload the plugin config - PluginConfigDOM?.Dispose(); + //All plugins must be unloaded forst + UnloadAll(); + //Reload the assembly and - await InitLoaderAsync(); - //fire after loaded - OnAfterReloaded?.Invoke(this, eventArgs); - //Raise the external reloaded event - Reloaded?.Invoke(this, EventArgs.Empty); + await InitializeController(); + + //Load plugins + LoadPlugins(); } catch (Exception ex) { - Log?.Error(ex); + Log?.Error("Failed reload plugins for {loader}\n{ex}", PluginPath, ex); } } /// - /// Initializes the plugin loader, the assembly, and all public - /// types + /// Initializes the plugin loader, and populates the + /// with initialized plugins. /// /// A task that represents the initialization - public async Task InitLoaderAsync() + /// + /// + public async Task InitializeController() { - //Load the main assembly - Assembly PluginAsm = Loader.LoadDefaultAssembly(); - //Get the plugin's configuration file - if (FileOperations.FileExists(PluginConfigPath)) + JsonDocument? pluginConfig = null; + + try { - //Open and read the config file - await using FileStream confStream = File.OpenRead(PluginConfigPath); - JsonDocumentOptions jdo = new() + //Get the plugin's configuration file + if (FileOperations.FileExists(PluginConfigPath)) { - AllowTrailingCommas = true, - CommentHandling = JsonCommentHandling.Skip, - }; - //parse the plugin config file - PluginConfigDOM = await JsonDocument.ParseAsync(confStream, jdo); - //Store the config loader args - if (PluginConfigDOM.RootElement.TryGetProperty("loader_args", out JsonElement loaderEl)) + pluginConfig = await this.GetPluginConfigAsync(); + } + else { - LoaderArgs = loaderEl; + //Set plugin config dom to an empty object if the file does not exist + pluginConfig = JsonDocument.Parse("{}"); } + + //Load the main assembly + Assembly PluginAsm = Loader.LoadDefaultAssembly(); + + //Init container from the assembly + Controller.InitializePlugins(PluginAsm); + + string[] cliArgs = Environment.GetCommandLineArgs(); + + //Configure log/doms + Controller.ConfigurePlugins(HostConfig, pluginConfig, cliArgs); } - else - { - //Set plugin config dom to an empty object if the file does not exist - PluginConfigDOM = JsonDocument.Parse("{}"); - LoaderArgs = null; - } - - string[] cliArgs = Environment.GetCommandLineArgs(); - - //Get all types that implement the IPlugin interface - IEnumerable plugins = PluginAsm.GetTypes().Where(static type => !type.IsAbstract && typeof(IPlugin).IsAssignableFrom(type)) - //Create the plugin instances - .Select(static type => (Activator.CreateInstance(type) as IPlugin)!); - //Load all plugins that implement the Iplugin interface - foreach (IPlugin plugin in plugins) + finally { - //Load wrapper - LivePlugin lp = new(plugin); - try - { - //Init config - lp.InitConfig(HostConfig, PluginConfigDOM); - //Init log handler - lp.InitLog(cliArgs); - //Load the plugin - lp.LoadPlugin(); - //Create new plugin loader for the plugin - LoadedPlugins.AddLast(lp); - } - catch (TargetInvocationException te) when (te.InnerException is not null) - { - throw te.InnerException; - } + pluginConfig?.Dispose(); } } + + /// + /// 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() + { + //Load all plugins + Controller.LoadPlugins(); + } + /// /// Manually reload the internal - /// which will reload the assembly and its plugins and endpoints + /// which will reload the assembly and its plugins /// - public void ReloadPlugin() => Loader.Reload(); + public void ReloadPlugins() => Loader.Reload(); /// /// Attempts to unload all plugins. /// /// - public void UnloadAll() => LoadedPlugins.TryForeach(lp => lp.UnloadPlugin(Log)); + public void UnloadAll() => Controller.UnloadPlugins(); /// protected override void Free() { + Controller.Dispose(); Loader.Dispose(); - PluginConfigDOM?.Dispose(); } } diff --git a/lib/Plugins.Runtime/src/VNLib.Plugins.Runtime.csproj b/lib/Plugins.Runtime/src/VNLib.Plugins.Runtime.csproj index 37a9e74..87dbcfe 100644 --- a/lib/Plugins.Runtime/src/VNLib.Plugins.Runtime.csproj +++ b/lib/Plugins.Runtime/src/VNLib.Plugins.Runtime.csproj @@ -5,7 +5,6 @@ net6.0 VNLib.Plugins.Runtime VNLib.Plugins.Runtime - 1.0.1.1 latest-all false True @@ -44,7 +43,7 @@ - + -- cgit