From 4ef0142747c4a0dd0c4cb71d8e7359c03b3a2942 Mon Sep 17 00:00:00 2001 From: vnugent Date: Tue, 2 Jan 2024 02:33:05 -0500 Subject: breaking changes: plugin service pools & move plugin api away from web related --- .../src/Construction/SsBuilderExtensions.cs | 6 +- .../src/IManagedPlugin.cs | 8 +- .../src/IManualPlugin.cs | 8 +- .../src/PluginExtensions.cs | 22 ++-- .../src/PluginLoadEventListener.cs | 2 +- .../src/PluginManager.cs | 2 +- .../src/PluginStackInitializer.cs | 27 +---- .../src/ServiceGroup.cs | 6 +- lib/Plugins.Essentials/src/IEndpoint.cs | 37 +++++++ .../src/Runtime/IVirtualEndpointDefinition.cs | 41 ++++++++ lib/Plugins.PluginBase/src/PluginBase.cs | 61 ++++++----- lib/Plugins.PluginBase/src/ServiceExport.cs | 37 +++++++ lib/Plugins.Runtime/src/LivePlugin.cs | 19 +++- lib/Plugins.Runtime/src/LoaderExtensions.cs | 90 +++++++++++++++- lib/Plugins.Runtime/src/PluginController.cs | 21 +++- lib/Plugins.Runtime/src/RuntimePluginLoader.cs | 4 +- .../src/Services/PluginServiceExport.cs | 49 +++++++++ .../src/Services/PluginServicePool.cs | 53 ++++++++++ .../src/SharedPluginServiceProvider.cs | 115 +++++++++++++++++++++ .../src/Attributes/ServiceConfiguratorAttribute.cs | 58 ----------- lib/Plugins/src/ExportFlags.cs | 40 +++++++ lib/Plugins/src/IPlugin.cs | 13 ++- lib/Plugins/src/IPluginServicePool.cs | 42 ++++++++ lib/Plugins/src/Web/IEndpoint.cs | 37 ------- lib/Plugins/src/Web/IWebPlugin.cs | 45 -------- 25 files changed, 610 insertions(+), 233 deletions(-) create mode 100644 lib/Plugins.Essentials/src/IEndpoint.cs create mode 100644 lib/Plugins.Essentials/src/Runtime/IVirtualEndpointDefinition.cs create mode 100644 lib/Plugins.PluginBase/src/ServiceExport.cs create mode 100644 lib/Plugins.Runtime/src/Services/PluginServiceExport.cs create mode 100644 lib/Plugins.Runtime/src/Services/PluginServicePool.cs create mode 100644 lib/Plugins.Runtime/src/SharedPluginServiceProvider.cs delete mode 100644 lib/Plugins/src/Attributes/ServiceConfiguratorAttribute.cs create mode 100644 lib/Plugins/src/ExportFlags.cs create mode 100644 lib/Plugins/src/IPluginServicePool.cs delete mode 100644 lib/Plugins/src/Web/IEndpoint.cs delete mode 100644 lib/Plugins/src/Web/IWebPlugin.cs (limited to 'lib') diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs index ee49f99..d96809b 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack @@ -185,9 +185,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction private static void OnPluginServiceEvent(this IManagedPlugin plugin, Action loader) { - object? service = plugin.Services.GetService(typeof(T)); - - if (service is T s) + if (plugin.Services.GetService(typeof(T)) is T s) { loader(s); } diff --git a/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs index 21c8e91..8332f7e 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack @@ -41,12 +41,6 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// IServiceContainer Services { get; } - /// - /// Internal call to get all exported plugin endpoints - /// - /// - internal IEndpoint[] GetEndpoints(); - /// /// Internal notification that the plugin is loaded /// diff --git a/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs index e58067f..cecd481 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack @@ -44,12 +44,6 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// The container to add services to void GetAllExportedServices(IServiceContainer container); - /// - /// Collects all exported endpoints to put into service - /// - /// The collection of endpoints - IEndpoint[] GetEndpoints(); - /// /// Initializes the plugin, called before accessing any other methods /// diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs index d93df6d..d8cdf75 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack @@ -26,7 +26,7 @@ using System.Linq; using System.Collections.Generic; using VNLib.Utils.Logging; -using VNLib.Plugins.Runtime; +using VNLib.Plugins.Essentials.Runtime; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -40,14 +40,18 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// /// The enumeration of web endpoints - internal static IEnumerable GetEndpoints(this IPlugin plugin) => ((IWebPlugin)plugin).GetEndpoints(); + internal static IEnumerable GetEndpoints(this IManagedPlugin plugin) + { + //Try to get the endpoint defintion + if (plugin.Services.GetService(typeof(IVirtualEndpointDefinition)) is IVirtualEndpointDefinition defintion) + { + //Return the endpoints from the definition + return defintion.GetEndpoints(); + } - /// - /// Gets only plugins that implement interface - /// - /// - /// - internal static IEnumerable GetOnlyWebPlugins(this PluginController controller) => controller.Plugins.Where(static p => p.Plugin is IWebPlugin); + //If the plugin does not have an endpoint definition, return an empty enumeration + return Enumerable.Empty(); + } /// /// Loads all plugins that implement interface into the diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginLoadEventListener.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadEventListener.cs index 0eaa3a8..b24019b 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginLoadEventListener.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadEventListener.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs index ba3b91a..f5f2abc 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs index c2ff1e4..e6489c9 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs @@ -26,7 +26,6 @@ using System; using System.IO; using System.Linq; -using System.Reflection; using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; @@ -35,7 +34,7 @@ using System.ComponentModel.Design; using VNLib.Utils.Logging; using VNLib.Plugins.Runtime; using VNLib.Utils.Extensions; -using VNLib.Plugins.Attributes; +using VNLib.Plugins.Runtime.Services; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -229,9 +228,6 @@ namespace VNLib.Plugins.Essentials.ServiceStack } } - /// - IEndpoint[] IManagedPlugin.GetEndpoints() => Plugin.Controller.GetOnlyWebPlugins().SelectMany(static pl => pl.Plugin!.GetEndpoints()).ToArray(); - /* * Automatically called after the plugin has successfully loaded * by event handlers below @@ -246,21 +242,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack //Init new service container _services = new(); - //Get types from plugin - foreach (LivePlugin plugin in Plugin.Controller.Plugins) - { - /* - * Get the exposed configurator method if declared, - * it may not be defined. - */ - ServiceConfigurator? callback = plugin.PluginType.GetMethods() - .Where(static m => m.GetCustomAttribute() != null && !m.IsAbstract) - .Select(m => m.CreateDelegate(plugin.Plugin)) - .FirstOrDefault(); - - //Invoke if defined to expose services - callback?.Invoke(_services); - } + //Get all exported services and add them to the container + PluginServiceExport[] exports = Plugin.Controller.GetExportedServices(); + Array.ForEach(exports, e => _services.AddService(e.ServiceType, e.Service, true)); } /// @@ -298,9 +282,6 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// public IServiceContainer Services => _container; - /// - IEndpoint[] IManagedPlugin.GetEndpoints() => Plugin.GetEndpoints(); - public void Load() { Plugin.Load(); diff --git a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs index e504013..da34d54 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.ServiceStack @@ -23,6 +23,7 @@ */ using System.Net; +using System.Linq; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -82,7 +83,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack internal void OnPluginLoaded(IManagedPlugin plugin) { //Get all new endpoints for plugin - IEndpoint[] newEndpoints = plugin.GetEndpoints(); + IEndpoint[] newEndpoints = plugin.GetEndpoints() + .ToArray(); //Add endpoints to dict _endpointsForPlugins.AddOrUpdate(plugin, newEndpoints); diff --git a/lib/Plugins.Essentials/src/IEndpoint.cs b/lib/Plugins.Essentials/src/IEndpoint.cs new file mode 100644 index 0000000..33d49df --- /dev/null +++ b/lib/Plugins.Essentials/src/IEndpoint.cs @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins +* File: IEndpoint.cs +* +* IEndpoint.cs is part of VNLib.Plugins which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. +*/ + +namespace VNLib.Plugins +{ + /// + /// A base class for all entity processing endpoints to listen for requests + /// + public interface IEndpoint + { + /// + /// The location path for which to match this handler + /// + public string Path { get; } + } +} \ No newline at end of file diff --git a/lib/Plugins.Essentials/src/Runtime/IVirtualEndpointDefinition.cs b/lib/Plugins.Essentials/src/Runtime/IVirtualEndpointDefinition.cs new file mode 100644 index 0000000..e505902 --- /dev/null +++ b/lib/Plugins.Essentials/src/Runtime/IVirtualEndpointDefinition.cs @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials +* File: IVirtualEndpointDefinition.cs +* +* IVirtualEndpointDefinition.cs is part of VNLib.Plugins.Essentials which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials 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 3 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials 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 System.Collections.Generic; + +namespace VNLib.Plugins.Essentials.Runtime +{ + /// + /// Represents a runtime factory for exporting + /// instances + /// + public interface IVirtualEndpointDefinition + { + /// + /// Gets all routable endpoints + /// + /// The endpoint enumeration + IEnumerable GetEndpoints(); + } +} \ No newline at end of file diff --git a/lib/Plugins.PluginBase/src/PluginBase.cs b/lib/Plugins.PluginBase/src/PluginBase.cs index a667a8f..d8b0973 100644 --- a/lib/Plugins.PluginBase/src/PluginBase.cs +++ b/lib/Plugins.PluginBase/src/PluginBase.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.PluginBase @@ -44,7 +44,7 @@ namespace VNLib.Plugins /// Provides a concrete base class for instances using the Serilog logging provider. /// Accepts the standard plugin configuration constructors /// - public abstract class PluginBase : MarshalByRefObject, IWebPlugin, IPluginTaskObserver + public abstract class PluginBase : MarshalByRefObject, IPluginTaskObserver, IPlugin { /* * CTS exists for the life of the plugin, its resources are never disposed @@ -53,6 +53,7 @@ namespace VNLib.Plugins private readonly CancellationTokenSource Cts = new(); private readonly LinkedList DeferredTasks = new(); + private readonly LinkedList _services = new(); /// /// A cancellation token that is cancelled when the plugin has been unloaded @@ -71,13 +72,6 @@ namespace VNLib.Plugins /// protected virtual string PluginConfigDomPropertyName => "plugin"; - /// - /// A list of all currently prepared endpoints. - /// Endpoints must be added to this list before is called - /// by the host app - /// - public ICollection Endpoints { get; } = new List(); - /// /// The logging instance /// @@ -98,6 +92,12 @@ namespace VNLib.Plugins /// public JsonElement PluginConfig => Configuration.RootElement.GetProperty(PluginConfigDomPropertyName); + /// + /// The collection of exported services that will be published to the host + /// application + /// + public ICollection Services => _services; + /// public abstract string PluginName { get; } @@ -257,18 +257,17 @@ namespace VNLib.Plugins Log.Error(ex); } } + /// /// Invoked when the host process has a command message to send /// /// The command message protected abstract void ProcessHostCommand(string cmd); - IEnumerable IWebPlugin.GetEndpoints() - { - OnGetEndpoints(); - return Endpoints; - } - + /// + void IPlugin.PublishServices(IPluginServicePool pool) => OnPublishServices(pool); + + /// void IPlugin.Load() { //Setup empty log if not specified @@ -291,7 +290,8 @@ namespace VNLib.Plugins throw; } } - + + /// void IPlugin.Unload() { try @@ -317,8 +317,8 @@ namespace VNLib.Plugins Configuration?.Dispose(); //dispose the log (Log as IDisposable)?.Dispose(); - //Clear endpoints list - Endpoints.Clear(); + //Remove any services + _services.Clear(); //empty deffered array DeferredTasks.Clear(); } @@ -371,12 +371,6 @@ namespace VNLib.Plugins } } - /// - /// Adds the specified endpoint to be routed when loading is complete - /// - /// The to present to the application when loaded - public void Route(IEndpoint endpoint) => Endpoints.Add(endpoint); - /// /// /// Invoked when the host loads the plugin instance @@ -391,11 +385,22 @@ namespace VNLib.Plugins /// Invoked when all endpoints have been removed from service. All managed and unmanged resources should be released. /// protected abstract void OnUnLoad(); - + /// - /// Invoked before called by the host app to get all endpoints - /// for the current plugin + /// Invoked when the host requests the plugin to publish services + /// + /// If overriden, the base implementation must be called to + /// publish all services in the internal service collection + /// /// - protected virtual void OnGetEndpoints() { } + /// The pool to publish services to + protected virtual void OnPublishServices(IPluginServicePool pool) + { + //Publish all services then cleanup + foreach (ServiceExport export in _services) + { + pool.ExportService(export.ServiceType, export.Service, export.Flags); + } + } } } \ No newline at end of file diff --git a/lib/Plugins.PluginBase/src/ServiceExport.cs b/lib/Plugins.PluginBase/src/ServiceExport.cs new file mode 100644 index 0000000..960f286 --- /dev/null +++ b/lib/Plugins.PluginBase/src/ServiceExport.cs @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.PluginBase +* File: ServiceExport.cs +* +* ServiceExport.cs is part of VNLib.Plugins.PluginBase which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.PluginBase 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.PluginBase 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.PluginBase. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Plugins +{ + /// + /// A service export that will be published to the + /// host application after the plugin has been loaded + /// + /// The exported service instance + /// The exported service type + /// The name of the service + public sealed record ServiceExport(Type ServiceType, object Service, ExportFlags Flags); +} \ No newline at end of file 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 @@ -129,6 +129,23 @@ namespace VNLib.Plugins.Runtime logFunc?.Invoke(cliArgs); } + /// + /// Gets services from the plugin if it is loaded and + /// publishes them to the pool + /// + /// The service pool to collect services into + /// + internal void GetServices(IPluginServicePool pool) + { + if (!_loaded) + { + throw new InvalidOperationException("Plugin is not loaded"); + } + + //Load services into pool + Plugin?.PublishServices(pool); + } + /// /// Invokes the plugins console event handler if the type has one /// and the plugin is loaded. 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 { + + /// + /// A callback function signature for plugin plugin loading errors on plugin + /// stacks. + /// + /// The loader that the exception occured on + /// The exception cause of the error + public delegate void PluginLoadErrorHandler(RuntimePluginLoader Loader, Exception exception); + /// /// Contains extension methods for PluginLoader library /// @@ -169,14 +179,68 @@ namespace VNLib.Plugins.Runtime /// Invokes the load method for all plugin instances /// /// + /// A value that indicates if plugins should be loaded concurrently or sequentially /// /// - public static void InvokeLoad(this IPluginStack runtime) + public static void InvokeLoad(this IPluginStack runtime, bool concurrent) + { + List 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); + } + } + + /// + /// Invokes the load method for all plugin instances, and captures exceptions + /// into the specified callback function. + /// + /// + /// A value that indicates if plugins should be loaded concurrently or sequentially + /// A callback function to handle error conditions instead of raising exceptions + /// + 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); + } + } + } } /// @@ -397,6 +461,24 @@ namespace VNLib.Plugins.Runtime return builder; } + /// + /// Registers a new for the current plugin stack + /// that will listen for plugin events and capture the exported services into a + /// single pool. + /// + /// + /// A new that will capture all exported services when loaded + public static SharedPluginServiceProvider RegisterServiceProvider(this IPluginStack stack) + { + //Init new service provider + SharedPluginServiceProvider provider = new(); + + //Register for all plugins + RegsiterListener(stack, provider); + + return provider; + } + /// /// Gets the current collection of loaded plugins for the plugin stack /// @@ -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 { + /// /// Manages the lifetime of a collection of instances, /// and their dependent event listeners @@ -52,11 +54,14 @@ namespace VNLib.Plugins.Runtime private readonly List _plugins; private readonly List> _listeners; + private readonly PluginServicePool _servicePool; + internal PluginController() { _plugins = new (); _listeners = new (); + _servicePool = new (); } /// @@ -86,6 +91,10 @@ namespace VNLib.Plugins.Runtime } } + /// + /// Populates the given with all services + /// + 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 +{ + /// + /// An immutable wrapper for an exported service by an + /// + public readonly record struct PluginServiceExport + { + /// + /// The exported service type + /// + public readonly Type ServiceType { get; init; } + + /// + /// The exported service instance + /// + public readonly object Service { get; init; } + + /// + /// The export flags + /// + 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 _services = new(); + + /// + 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 +{ + /// + /// Represents a single shared pool for a collection of plugins to + /// export services to. + /// + public sealed class SharedPluginServiceProvider : + VnDisposeable, + IServiceProvider, + IPluginEventListener + { + private readonly ServiceContainer _serviceContainer = new(); + private readonly object _syncRoot = new(); + + /// + public object? GetService(Type serviceType) => _serviceContainer.GetService(serviceType); + + /// + /// Gets the service object of the specified type. + /// + /// + /// + /// A service object of type serviceType. -or- null if + /// there is no service object of type serviceType. + /// + public T? GetService() 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(); + } + } + } + } + + /// + protected override void Free() => _serviceContainer.Dispose(); + } +} diff --git a/lib/Plugins/src/Attributes/ServiceConfiguratorAttribute.cs b/lib/Plugins/src/Attributes/ServiceConfiguratorAttribute.cs deleted file mode 100644 index 123fb66..0000000 --- a/lib/Plugins/src/Attributes/ServiceConfiguratorAttribute.cs +++ /dev/null @@ -1,58 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins -* File: ServiceConfiguratorAttribute.cs -* -* ServiceConfiguratorAttribute.cs is part of VNLib.Plugins which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. -*/ - -using System; -using System.ComponentModel.Design; - -namespace VNLib.Plugins.Attributes -{ - /// - /// - /// Declare this attribute on an instance method to define the service configuration - /// method. When declared, allows the plugin to expose shared types to the host - /// - /// - /// This method may be runtime dependant, it may not be called on all platforms, and it - /// may not be required. - /// - /// - /// Lifecycle: This method may be called by the runtime anytime after the - /// method, its exposed services are considered invalid after the - /// method is called. - /// - /// - /// Method signature: void OnServiceConfiguration( container) - /// - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public sealed class ServiceConfiguratorAttribute : Attribute - { } - - /// - /// A safe delegate that matches the signature of the - /// method exposed - /// - /// - public delegate void ServiceConfigurator(IServiceContainer sender); -} diff --git a/lib/Plugins/src/ExportFlags.cs b/lib/Plugins/src/ExportFlags.cs new file mode 100644 index 0000000..25c1f9f --- /dev/null +++ b/lib/Plugins/src/ExportFlags.cs @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins +* File: ExportFlags.cs +* +* ExportFlags.cs is part of VNLib.Plugins which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Plugins +{ + /// + /// Service export flags + /// + [Flags] + public enum ExportFlags + { + /// + /// No flags + /// + None = 0, + } +} \ No newline at end of file diff --git a/lib/Plugins/src/IPlugin.cs b/lib/Plugins/src/IPlugin.cs index c1fa6a6..cf581d6 100644 --- a/lib/Plugins/src/IPlugin.cs +++ b/lib/Plugins/src/IPlugin.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins @@ -43,5 +43,16 @@ namespace VNLib.Plugins /// Invoked when the plugin is unloaded from the runtime /// void Unload(); + + /// + /// Called directly after the plugin is loaded to allow for service + /// publishing for the host application to use + /// + /// NOTE: A service pool is shared for each assembly, if there are multiple + /// plugins per assembly, they will share the same service pool. + /// + /// + /// The service publisher instance + void PublishServices(IPluginServicePool pool); } } \ No newline at end of file diff --git a/lib/Plugins/src/IPluginServicePool.cs b/lib/Plugins/src/IPluginServicePool.cs new file mode 100644 index 0000000..a4c5e9a --- /dev/null +++ b/lib/Plugins/src/IPluginServicePool.cs @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins +* File: IPluginServicePool.cs +* +* IPluginServicePool.cs is part of VNLib.Plugins which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Plugins +{ + /// + /// Represents a type that exposes services to the loading application + /// + public interface IPluginServicePool + { + /// + /// Publishes a generic service to the service pool + /// + /// The to expose to the pool for searching + /// The service instance to publish + /// Optional flags to pass during export + void ExportService(Type serviceType, object service, ExportFlags flags = ExportFlags.None); + } +} \ No newline at end of file diff --git a/lib/Plugins/src/Web/IEndpoint.cs b/lib/Plugins/src/Web/IEndpoint.cs deleted file mode 100644 index 33d49df..0000000 --- a/lib/Plugins/src/Web/IEndpoint.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins -* File: IEndpoint.cs -* -* IEndpoint.cs is part of VNLib.Plugins which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. -*/ - -namespace VNLib.Plugins -{ - /// - /// A base class for all entity processing endpoints to listen for requests - /// - public interface IEndpoint - { - /// - /// The location path for which to match this handler - /// - public string Path { get; } - } -} \ No newline at end of file diff --git a/lib/Plugins/src/Web/IWebPlugin.cs b/lib/Plugins/src/Web/IWebPlugin.cs deleted file mode 100644 index ed080d0..0000000 --- a/lib/Plugins/src/Web/IWebPlugin.cs +++ /dev/null @@ -1,45 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins -* File: IWebPlugin.cs -* -* IWebPlugin.cs is part of VNLib.Plugins which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins 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 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. If not, see http://www.gnu.org/licenses/. -*/ - -using System.Collections.Generic; - -namespace VNLib.Plugins -{ - /// - /// Represents a plugin that is expected to perform web application based operations - /// - public interface IWebPlugin : IPlugin - { - /// - /// Returns all endpoints within the plugin to load into the current root - /// - /// An enumeration of endpoints to load - /// - /// Lifecycle: Results returned from this method should be consistant (although its only - /// likely to be called once) anytime after the method, and undefined - /// after the method is called. - /// - IEnumerable GetEndpoints(); - } -} \ No newline at end of file -- cgit