diff options
author | vnugent <public@vaughnnugent.com> | 2024-01-02 02:33:05 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-01-02 02:33:05 -0500 |
commit | 4ef0142747c4a0dd0c4cb71d8e7359c03b3a2942 (patch) | |
tree | 2991941aec6a0b981411a4f4bb83d8d2ad900aba /lib/Plugins.Runtime | |
parent | a6b628c8653485a803bdbe322e2ecd2a69fc8e5a (diff) |
breaking changes: plugin service pools & move plugin api away from web related
Diffstat (limited to 'lib/Plugins.Runtime')
-rw-r--r-- | lib/Plugins.Runtime/src/LivePlugin.cs | 19 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/LoaderExtensions.cs | 90 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/PluginController.cs | 21 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/RuntimePluginLoader.cs | 4 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/Services/PluginServiceExport.cs | 49 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/Services/PluginServicePool.cs | 53 | ||||
-rw-r--r-- | lib/Plugins.Runtime/src/SharedPluginServiceProvider.cs | 115 |
7 files changed, 341 insertions, 10 deletions
diff --git a/lib/Plugins.Runtime/src/LivePlugin.cs b/lib/Plugins.Runtime/src/LivePlugin.cs index 573b520..3ed2ad6 100644 --- a/lib/Plugins.Runtime/src/LivePlugin.cs +++ b/lib/Plugins.Runtime/src/LivePlugin.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime @@ -130,6 +130,23 @@ namespace VNLib.Plugins.Runtime } /// <summary> + /// Gets services from the plugin if it is loaded and + /// publishes them to the pool + /// </summary> + /// <param name="pool">The service pool to collect services into</param> + /// <exception cref="InvalidOperationException"></exception> + internal void GetServices(IPluginServicePool pool) + { + if (!_loaded) + { + throw new InvalidOperationException("Plugin is not loaded"); + } + + //Load services into pool + Plugin?.PublishServices(pool); + } + + /// <summary> /// Invokes the plugins console event handler if the type has one /// and the plugin is loaded. /// </summary> diff --git a/lib/Plugins.Runtime/src/LoaderExtensions.cs b/lib/Plugins.Runtime/src/LoaderExtensions.cs index d167488..b892213 100644 --- a/lib/Plugins.Runtime/src/LoaderExtensions.cs +++ b/lib/Plugins.Runtime/src/LoaderExtensions.cs @@ -27,6 +27,7 @@ using System.IO; using System.Linq; using System.Text; using System.Text.Json; +using System.Threading.Tasks; using System.Collections.Generic; using VNLib.Utils.IO; @@ -34,6 +35,15 @@ using VNLib.Utils.Extensions; namespace VNLib.Plugins.Runtime { + + /// <summary> + /// A callback function signature for plugin plugin loading errors on plugin + /// stacks. + /// </summary> + /// <param name="Loader">The loader that the exception occured on</param> + /// <param name="exception">The exception cause of the error</param> + public delegate void PluginLoadErrorHandler(RuntimePluginLoader Loader, Exception exception); + /// <summary> /// Contains extension methods for PluginLoader library /// </summary> @@ -169,14 +179,68 @@ namespace VNLib.Plugins.Runtime /// Invokes the load method for all plugin instances /// </summary> /// <param name="runtime"></param> + /// <param name="concurrent">A value that indicates if plugins should be loaded concurrently or sequentially</param> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="AggregateException"></exception> - public static void InvokeLoad(this IPluginStack runtime) + public static void InvokeLoad(this IPluginStack runtime, bool concurrent) + { + List<Exception> exceptions = new (); + + //Add load exceptions into the list + void onError(RuntimePluginLoader loader, Exception ex) => exceptions.Add(ex); + + //Invoke load with onError callback + InvokeLoad(runtime, concurrent, onError); + + //If any exceptions occured, throw them now + if(exceptions.Count > 0) + { + throw new AggregateException(exceptions); + } + } + + /// <summary> + /// Invokes the load method for all plugin instances, and captures exceptions + /// into the specified callback function. + /// </summary> + /// <param name="runtime"></param> + /// <param name="concurrent">A value that indicates if plugins should be loaded concurrently or sequentially</param> + /// <param name="onError">A callback function to handle error conditions instead of raising exceptions</param> + /// <exception cref="ArgumentNullException"></exception> + public static void InvokeLoad(this IPluginStack runtime, bool concurrent, PluginLoadErrorHandler onError) { ArgumentNullException.ThrowIfNull(runtime, nameof(runtime)); - //try loading all plugins - runtime.Plugins.TryForeach(static p => p.LoadPlugins()); + if (concurrent) + { + //Invoke load in parallel + Parallel.ForEach(runtime.Plugins, p => + { + try + { + p.LoadPlugins(); + } + catch (Exception ex) + { + onError(p, ex); + } + }); + } + else + { + //Load sequentially + foreach(RuntimePluginLoader loader in runtime.Plugins) + { + try + { + loader.LoadPlugins(); + } + catch (Exception ex) + { + onError(loader, ex); + } + } + } } /// <summary> @@ -398,6 +462,24 @@ namespace VNLib.Plugins.Runtime } /// <summary> + /// Registers a new <see cref="SharedPluginServiceProvider"/> for the current plugin stack + /// that will listen for plugin events and capture the exported services into a + /// single pool. + /// </summary> + /// <param name="stack"></param> + /// <returns>A new <see cref="SharedPluginServiceProvider"/> that will capture all exported services when loaded</returns> + public static SharedPluginServiceProvider RegisterServiceProvider(this IPluginStack stack) + { + //Init new service provider + SharedPluginServiceProvider provider = new(); + + //Register for all plugins + RegsiterListener(stack, provider); + + return provider; + } + + /// <summary> /// Gets the current collection of loaded plugins for the plugin stack /// </summary> /// <param name="stack"></param> @@ -499,6 +581,6 @@ namespace VNLib.Plugins.Runtime { //Do nothing } - } + } } } diff --git a/lib/Plugins.Runtime/src/PluginController.cs b/lib/Plugins.Runtime/src/PluginController.cs index adb8ff9..7f82c13 100644 --- a/lib/Plugins.Runtime/src/PluginController.cs +++ b/lib/Plugins.Runtime/src/PluginController.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime @@ -29,9 +29,11 @@ using System.Collections.Generic; using VNLib.Utils.IO; using VNLib.Utils.Extensions; +using VNLib.Plugins.Runtime.Services; namespace VNLib.Plugins.Runtime { + /// <summary> /// Manages the lifetime of a collection of <see cref="IPlugin"/> instances, /// and their dependent event listeners @@ -52,11 +54,14 @@ namespace VNLib.Plugins.Runtime private readonly List<LivePlugin> _plugins; private readonly List<KeyValuePair<IPluginEventListener, object?>> _listeners; + private readonly PluginServicePool _servicePool; + internal PluginController() { _plugins = new (); _listeners = new (); + _servicePool = new (); } /// <summary> @@ -86,6 +91,10 @@ namespace VNLib.Plugins.Runtime } } + /// <summary> + /// Populates the given <see cref="IServiceContainer"/> with all services + /// </summary> + public PluginServiceExport[] GetExportedServices() => _servicePool.GetServices(); internal void InitializePlugins(Assembly asm) { @@ -121,6 +130,9 @@ namespace VNLib.Plugins.Runtime //Load all plugins _plugins.TryForeach(static p => p.LoadPlugin()); + //Load all services into the service pool + _plugins.TryForeach(p => p.GetServices(_servicePool)); + //Notify event handlers _listeners.TryForeach(l => l.Key.OnPluginLoaded(this, l.Value)); } @@ -140,8 +152,10 @@ namespace VNLib.Plugins.Runtime } finally { - //Always + //Always clear plugins _plugins.Clear(); + //always make sure service pool is clear + _servicePool.Clear(); } } } @@ -150,7 +164,8 @@ namespace VNLib.Plugins.Runtime { _plugins.Clear(); _listeners.Clear(); + _servicePool.Clear(); } - + } } diff --git a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs index cef4d47..40cf5ed 100644 --- a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs +++ b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime @@ -199,4 +199,4 @@ namespace VNLib.Plugins.Runtime Loader.Dispose(); } } -}
\ No newline at end of file +} diff --git a/lib/Plugins.Runtime/src/Services/PluginServiceExport.cs b/lib/Plugins.Runtime/src/Services/PluginServiceExport.cs new file mode 100644 index 0000000..0053a70 --- /dev/null +++ b/lib/Plugins.Runtime/src/Services/PluginServiceExport.cs @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: PluginServiceExport.cs +* +* PluginServiceExport.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.Services +{ + /// <summary> + /// An immutable wrapper for an exported service by an <see cref="IPlugin"/> + /// </summary> + public readonly record struct PluginServiceExport + { + /// <summary> + /// The exported service type + /// </summary> + public readonly Type ServiceType { get; init; } + + /// <summary> + /// The exported service instance + /// </summary> + public readonly object Service { get; init; } + + /// <summary> + /// The export flags + /// </summary> + public readonly ExportFlags Flags { get; init; } + } +} diff --git a/lib/Plugins.Runtime/src/Services/PluginServicePool.cs b/lib/Plugins.Runtime/src/Services/PluginServicePool.cs new file mode 100644 index 0000000..fc8a371 --- /dev/null +++ b/lib/Plugins.Runtime/src/Services/PluginServicePool.cs @@ -0,0 +1,53 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: PluginServicePool.cs +* +* PluginServicePool.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.Collections.Generic; + +namespace VNLib.Plugins.Runtime.Services +{ + internal sealed class PluginServicePool : IPluginServicePool + { + private readonly LinkedList<PluginServiceExport> _services = new(); + + ///<inheritdoc/> + public void ExportService(Type serviceType, object service, ExportFlags flags = ExportFlags.None) + { + PluginServiceExport wrapper = new() + { + Service = service, + ServiceType = serviceType, + Flags = flags + }; + + //Add service to container + _services.AddLast(wrapper); + } + + public PluginServiceExport[] GetServices() => _services.ToArray(); + + public void Clear() => _services.Clear(); + } +} diff --git a/lib/Plugins.Runtime/src/SharedPluginServiceProvider.cs b/lib/Plugins.Runtime/src/SharedPluginServiceProvider.cs new file mode 100644 index 0000000..a64ede3 --- /dev/null +++ b/lib/Plugins.Runtime/src/SharedPluginServiceProvider.cs @@ -0,0 +1,115 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Runtime +* File: SharedPluginServiceProvider.cs +* +* SharedPluginServiceProvider.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.ComponentModel.Design; + +using VNLib.Utils; +using VNLib.Plugins.Runtime.Services; + +namespace VNLib.Plugins.Runtime +{ + /// <summary> + /// Represents a single shared pool for a collection of plugins to + /// export services to. + /// </summary> + public sealed class SharedPluginServiceProvider : + VnDisposeable, + IServiceProvider, + IPluginEventListener + { + private readonly ServiceContainer _serviceContainer = new(); + private readonly object _syncRoot = new(); + + ///<inheritdoc/> + public object? GetService(Type serviceType) => _serviceContainer.GetService(serviceType); + + /// <summary> + /// Gets the service object of the specified type. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <returns> + /// A service object of type serviceType. -or- null if + /// there is no service object of type serviceType. + /// </returns> + public T? GetService<T>() where T : class => GetService(typeof(T)) as T; + + void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state) + { + //Add services + AddOrRemoveServices(controller, true); + } + + void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state) + { + //Remove services + AddOrRemoveServices(controller, false); + } + + private void AddOrRemoveServices(PluginController controller, bool add) + { + /* + * Depending on when services are loaded/unloaded, this instances + * may be disposeed so avoid raising an exception for a condition + * that doenst matter. If disposed, we dont need to clean anything up + */ + if (Disposed) + { + return; + } + + //Get all exported services + PluginServiceExport[] exports = controller.GetExportedServices(); + + //We need to hold a lock to synchronize access to the service container + lock (_syncRoot) + { + //if add flag is set, add the serivces, otherwise remove them + if (add) + { + Array.ForEach(exports, e => _serviceContainer.AddService(e.ServiceType, e.Service)); + } + else + { + Array.ForEach(exports, e => _serviceContainer.RemoveService(e.ServiceType)); + } + } + + //cleanup any disposable services when removing + if (!add) + { + foreach(PluginServiceExport export in exports) + { + if(export.Service is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } + + ///<inheritdoc/> + protected override void Free() => _serviceContainer.Dispose(); + } +} |