From 3ce61cf38727db2f37a0e478182d2a73222c8a7c Mon Sep 17 00:00:00 2001 From: vnugent Date: Sat, 15 Apr 2023 01:58:55 -0400 Subject: Managed assembly loading overhaul --- .../src/HttpServiceStackBuilder.cs | 5 +-- .../src/IPluginAssemblyLoaderFactory.cs | 43 ++++++++++++++++++++++ .../src/IPluginController.cs | 5 +-- .../src/ManagedPlugin.cs | 35 +++--------------- .../src/PluginLoadConfiguration.cs | 25 +++++-------- .../src/PluginManager.cs | 34 ++++++++--------- 6 files changed, 80 insertions(+), 67 deletions(-) create mode 100644 lib/Plugins.Essentials.ServiceStack/src/IPluginAssemblyLoaderFactory.cs (limited to 'lib/Plugins.Essentials.ServiceStack/src') diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs index 25b6d5f..ae3b736 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs @@ -23,7 +23,6 @@ */ using System; -using System.Threading.Tasks; using System.Collections.Generic; using VNLib.Net.Http; @@ -117,7 +116,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// The newly constructed that may be used to manage your http services /// - public async Task BuildAsync(PluginLoadConfiguration config, ILogProvider appLog) + public HttpServiceStack Build(IPluginLoadConfiguration config, ILogProvider appLog) { _ = _hostBuilder ?? throw new ArgumentNullException("WithDomainBuilder", "You have not configured a service domain configuration callback"); _ = _getServers ?? throw new ArgumentNullException("WithHttp", "You have not configured a IHttpServer configuration callback"); @@ -132,7 +131,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack } //Load plugins async - await sd.PluginManager.LoadPluginsAsync(config, appLog); + sd.PluginManager.LoadPlugins(config, appLog); LinkedList servers = new(); diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginAssemblyLoaderFactory.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginAssemblyLoaderFactory.cs new file mode 100644 index 0000000..4cb5741 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/IPluginAssemblyLoaderFactory.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: IPluginAssemblyLoaderFactory.cs +* +* IPluginAssemblyLoaderFactory.cs is part of VNLib.Plugins.Essentials.ServiceStack +* which is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero 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.Essentials.ServiceStack 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 Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using VNLib.Plugins.Runtime; + +namespace VNLib.Plugins.Essentials.ServiceStack +{ + /// + /// Represents a provider that creates a unique for the the + /// loaded assembly file. + /// + public interface IPluginAssemblyLoaderFactory + { + /// + /// Gets a new and unique instance for a given plugin assembly + /// file path + /// + /// The file path to the requested plugin assembly file + /// The new to recover the plugin assembly manifest + IPluginAssemblyLoader GetLoaderForPluginFile(string pluginFile); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs index fb9c340..3394208 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs @@ -23,7 +23,6 @@ */ using System; -using System.Threading.Tasks; using System.Collections.Generic; using VNLib.Utils.Logging; @@ -45,10 +44,10 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// Loads all plugins specified by the host config to the service manager, /// or attempts to load plugins by the default /// - /// The configuration instance to pass to plugins + /// The plugin loading configuration /// A log provider to write message and errors to /// A task that resolves when all plugins are loaded - Task LoadPluginsAsync(PluginLoadConfiguration config, ILogProvider appLog); + void LoadPlugins(IPluginLoadConfiguration config, ILogProvider appLog); /// /// Sends a message to a plugin identified by it's name. diff --git a/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs index 3bf99cb..54f0090 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs @@ -27,17 +27,12 @@ using System.IO; using System.Linq; using System.Threading; using System.Reflection; -using System.Runtime.Loader; -using System.Threading.Tasks; using System.ComponentModel.Design; -using McMaster.NETCore.Plugins; - using VNLib.Utils; using VNLib.Plugins.Runtime; using VNLib.Plugins.Attributes; - namespace VNLib.Plugins.Essentials.ServiceStack { @@ -48,39 +43,21 @@ namespace VNLib.Plugins.Essentials.ServiceStack private UnloadableServiceContainer? _services; - public ManagedPlugin(string pluginPath, in PluginLoadConfiguration config, IPluginEventListener listener) + public ManagedPlugin(RuntimePluginLoader loader, IPluginEventListener listener) { - PluginPath = pluginPath; - - //Get the plugin config for the assembly - PluginConfig pConfig = GetConfigForAssemblyPath(pluginPath, in config); - //configure the loader - _plugin = new(pConfig, config.HostConfig, config.PluginErrorLog); + _plugin = loader; - //Register listener before loading occurs + //Register loading event listener before loading occurs _plugin.Controller.Register(this, this); //Store listener to raise events _serviceDomainListener = listener; } - private static PluginConfig GetConfigForAssemblyPath(string asmPath, in PluginLoadConfiguration loadConfig) - { - PluginConfig config = new(asmPath) - { - IsUnloadable = loadConfig.HotReload, - EnableHotReload = loadConfig.HotReload, - IsLazyLoaded = false, - PreferSharedTypes = true, - DefaultContext = AssemblyLoadContext.Default, - ReloadDelay = loadConfig.ReloadDelay - }; - return config; - } /// - public string PluginPath { get; } + public string PluginPath => _plugin.Config.AssemblyFile; /// public IUnloadableServiceProvider Services @@ -104,10 +81,10 @@ namespace VNLib.Plugins.Essentials.ServiceStack internal string PluginFileName => Path.GetFileName(PluginPath); - internal Task InitializePluginsAsync() + internal void InitializePlugins() { Check(); - return _plugin.InitializeController(); + _plugin.InitializeController(); } internal void LoadPlugins() diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs index 894ae55..9ab9a82 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack -* File: PluginLoadConfiguration.cs +* File: IPluginLoadConfiguration.cs * -* PluginLoadConfiguration.cs is part of VNLib.Plugins.Essentials.ServiceStack +* IPluginLoadConfiguration.cs is part of VNLib.Plugins.Essentials.ServiceStack * which is part of the larger VNLib collection of libraries and utilities. * * VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify @@ -30,38 +30,33 @@ using VNLib.Plugins.Runtime; namespace VNLib.Plugins.Essentials.ServiceStack { + /// /// Plugin loading configuration variables /// - public readonly record struct PluginLoadConfiguration + public interface IPluginLoadConfiguration { /// /// The directory containing the dynamic plugin assemblies to load /// - public readonly string PluginDir { get; init; } - - /// - /// A value that indicates if the internal - /// allows for hot-reload/unloadable plugin assemblies. - /// - public readonly bool HotReload { get; init; } + string PluginDir { get; } /// /// The optional host configuration file to merge with plugin config /// to pass to the loading plugin. /// - public readonly JsonElement? HostConfig { get; init; } + JsonElement? HostConfig { get; } /// /// Passed to the underlying /// holding plugins /// - public readonly ILogProvider? PluginErrorLog { get; init; } + ILogProvider? PluginErrorLog { get; } /// - /// If hot-reload is enabled, sets a time delay the file watcher waits when - /// a plugin assembly has changed. + /// A factory instance the provides new instances + /// on demand from its plugin assembly path /// - public readonly TimeSpan ReloadDelay { get; init; } + public IPluginAssemblyLoaderFactory AssemblyLoaderFactory { get; init; } } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs index e4e5670..2013a58 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs @@ -66,7 +66,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// - public Task LoadPluginsAsync(PluginLoadConfiguration config, ILogProvider appLog) + public void LoadPlugins(IPluginLoadConfiguration config, ILogProvider appLog) { Check(); @@ -76,10 +76,10 @@ namespace VNLib.Plugins.Essentials.ServiceStack if (!dir.Exists) { appLog.Warn("Plugin directory {dir} does not exist. No plugins were loaded", config.PluginDir); - return Task.CompletedTask; + return; } - appLog.Information("Loading plugins. Hot-reload: {en}", config.HotReload); + appLog.Information("Loading managed plugins"); //Enumerate all dll files within this dir IEnumerable dirs = dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly); @@ -91,14 +91,21 @@ namespace VNLib.Plugins.Essentials.ServiceStack appLog.Debug("Found plugin files: \n{files}", string.Concat(pluginFileNames)); - //Initialze plugin managers - ManagedPlugin[] wrappers = pluginPaths.Select(pw => new ManagedPlugin(pw, config, this)).ToArray(); + /* + * We need to get the assembly loader for the plugin file, then create its + * RuntimePluginLoader which will be passed to the Managed plugin instance + */ + + ManagedPlugin[] wrappers = pluginPaths.Select(pw => config.AssemblyLoaderFactory.GetLoaderForPluginFile(pw)) + .Select(l => new RuntimePluginLoader(l, config.HostConfig, config.PluginErrorLog)) + .Select(loader => new ManagedPlugin(loader, this)) + .ToArray(); //Add to loaded plugins _plugins.AddRange(wrappers); //Load plugins - return InitiailzeAndLoadAsync(appLog); + InitiailzeAndLoad(appLog); } private static IEnumerable GetPluginPaths(IEnumerable dirs) @@ -118,13 +125,10 @@ namespace VNLib.Plugins.Essentials.ServiceStack }); } - private async Task InitiailzeAndLoadAsync(ILogProvider debugLog) + private void InitiailzeAndLoad(ILogProvider debugLog) { //Load all async - Task[] initAll = _plugins.Select(p => InitializePlugin(p, debugLog)).ToArray(); - - //Wait for initalization - await Task.WhenAll(initAll).ConfigureAwait(false); + _plugins.ToArray().TryForeach(p => InitializePlugin(p, debugLog)); //Load stage, load all multithreaded Parallel.ForEach(_plugins, p => LoadPlugin(p, debugLog)); @@ -132,7 +136,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack debugLog.Information("Plugin loading completed"); } - private async Task InitializePlugin(ManagedPlugin plugin, ILogProvider debugLog) + private void InitializePlugin(ManagedPlugin plugin, ILogProvider debugLog) { void LogAndRemovePlugin(Exception ex) { @@ -151,11 +155,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack try { //Load wrapper - await plugin.InitializePluginsAsync().ConfigureAwait(true); - } - catch(AggregateException ae) when (ae.InnerException != null) - { - LogAndRemovePlugin(ae.InnerException); + plugin.InitializePlugins(); } catch (Exception ex) { -- cgit