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 | |
parent | a6b628c8653485a803bdbe322e2ecd2a69fc8e5a (diff) |
breaking changes: plugin service pools & move plugin api away from web related
Diffstat (limited to 'lib')
23 files changed, 542 insertions, 165 deletions
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<T>(this IManagedPlugin plugin, Action<T> 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 @@ -42,12 +42,6 @@ namespace VNLib.Plugins.Essentials.ServiceStack IServiceContainer Services { get; } /// <summary> - /// Internal call to get all exported plugin endpoints - /// </summary> - /// <returns></returns> - internal IEndpoint[] GetEndpoints(); - - /// <summary> /// Internal notification that the plugin is loaded /// </summary> internal void OnPluginLoaded(); 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 @@ -45,12 +45,6 @@ namespace VNLib.Plugins.Essentials.ServiceStack void GetAllExportedServices(IServiceContainer container); /// <summary> - /// Collects all exported endpoints to put into service - /// </summary> - /// <returns>The collection of endpoints</returns> - IEndpoint[] GetEndpoints(); - - /// <summary> /// Initializes the plugin, called before accessing any other methods /// </summary> void Initialize(); 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 /// </summary> /// <param name="plugin"></param> /// <returns>The enumeration of web endpoints</returns> - internal static IEnumerable<IEndpoint> GetEndpoints(this IPlugin plugin) => ((IWebPlugin)plugin).GetEndpoints(); + internal static IEnumerable<IEndpoint> 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(); + } - /// <summary> - /// Gets only plugins that implement <see cref="IWebPlugin"/> interface - /// </summary> - /// <param name="controller"></param> - /// <returns></returns> - internal static IEnumerable<LivePlugin> 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<IEndpoint>(); + } /// <summary> /// Loads all plugins that implement <see cref="IWebPlugin"/> 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 } } - ///<inheritdoc/> - 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<ServiceConfiguratorAttribute>() != null && !m.IsAbstract) - .Select(m => m.CreateDelegate<ServiceConfigurator>(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)); } ///<inheritdoc/> @@ -298,9 +282,6 @@ namespace VNLib.Plugins.Essentials.ServiceStack ///<inheritdoc/> public IServiceContainer Services => _container; - ///<inheritdoc/> - 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/src/Web/IEndpoint.cs b/lib/Plugins.Essentials/src/IEndpoint.cs index 33d49df..33d49df 100644 --- a/lib/Plugins/src/Web/IEndpoint.cs +++ b/lib/Plugins.Essentials/src/IEndpoint.cs 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 +{ + /// <summary> + /// Represents a runtime factory for exporting + /// <see cref="IEndpoint"/> instances + /// </summary> + public interface IVirtualEndpointDefinition + { + /// <summary> + /// Gets all routable endpoints + /// </summary> + /// <returns>The endpoint enumeration</returns> + IEnumerable<IEndpoint> 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 <see cref="IPlugin"/> instances using the Serilog logging provider. /// Accepts the standard plugin <see cref="JsonDocument"/> configuration constructors /// </summary> - 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<Task> DeferredTasks = new(); + private readonly LinkedList<ServiceExport> _services = new(); /// <summary> /// A cancellation token that is cancelled when the plugin has been unloaded @@ -72,13 +73,6 @@ namespace VNLib.Plugins protected virtual string PluginConfigDomPropertyName => "plugin"; /// <summary> - /// A list of all currently prepared <see cref="IEndpoint"/> endpoints. - /// Endpoints must be added to this list before <see cref="IWebPlugin.GetEndpoints"/> is called - /// by the host app - /// </summary> - public ICollection<IEndpoint> Endpoints { get; } = new List<IEndpoint>(); - - /// <summary> /// The logging instance /// </summary> public ILogProvider Log { get; private set; } @@ -98,6 +92,12 @@ namespace VNLib.Plugins /// </summary> public JsonElement PluginConfig => Configuration.RootElement.GetProperty(PluginConfigDomPropertyName); + /// <summary> + /// The collection of exported services that will be published to the host + /// application + /// </summary> + public ICollection<ServiceExport> Services => _services; + /// <inheritdoc/> public abstract string PluginName { get; } @@ -257,18 +257,17 @@ namespace VNLib.Plugins Log.Error(ex); } } + /// <summary> /// Invoked when the host process has a command message to send /// </summary> /// <param name="cmd">The command message</param> protected abstract void ProcessHostCommand(string cmd); - IEnumerable<IEndpoint> IWebPlugin.GetEndpoints() - { - OnGetEndpoints(); - return Endpoints; - } - + ///<inheritdoc/> + void IPlugin.PublishServices(IPluginServicePool pool) => OnPublishServices(pool); + + ///<inheritdoc/> void IPlugin.Load() { //Setup empty log if not specified @@ -291,7 +290,8 @@ namespace VNLib.Plugins throw; } } - + + ///<inheritdoc/> 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(); } @@ -372,12 +372,6 @@ namespace VNLib.Plugins } /// <summary> - /// Adds the specified endpoint to be routed when loading is complete - /// </summary> - /// <param name="endpoint">The <see cref="IEndpoint"/> to present to the application when loaded</param> - public void Route(IEndpoint endpoint) => Endpoints.Add(endpoint); - - /// <summary> /// <para> /// Invoked when the host loads the plugin instance /// </para> @@ -391,11 +385,22 @@ namespace VNLib.Plugins /// Invoked when all endpoints have been removed from service. All managed and unmanged resources should be released. /// </summary> protected abstract void OnUnLoad(); - + /// <summary> - /// Invoked before <see cref="IWebPlugin.GetEndpoints"/> called by the host app to get all endpoints - /// for the current plugin + /// Invoked when the host requests the plugin to publish services + /// <para> + /// If overriden, the base implementation must be called to + /// publish all services in the internal service collection + /// </para> /// </summary> - protected virtual void OnGetEndpoints() { } + /// <param name="pool">The pool to publish services to</param> + 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 +{ + /// <summary> + /// A service export that will be published to the + /// host application after the plugin has been loaded + /// </summary> + /// <param name="Service"> The exported service instance </param> + /// <param name="ServiceType"> The exported service type </param> + /// <param name="Flags"> The name of the service </param> + 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 @@ -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(); + } +} 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 -{ - /// <summary> - /// <para> - /// Declare this attribute on an <see cref="IPlugin"/> instance method to define the service configuration - /// method. When declared, allows the plugin to expose shared types to the host - /// </para> - /// <para> - /// This method may be runtime dependant, it may not be called on all platforms, and it - /// may not be required. - /// </para> - /// <para> - /// Lifecycle: This method may be called by the runtime anytime after the <see cref="IPlugin.Load"/> - /// method, its exposed services are considered invalid after the <see cref="IPlugin.Unload"/> - /// method is called. - /// </para> - /// <para> - /// Method signature: <code>void OnServiceConfiguration(<see cref="IServiceContainer"/> container)</code> - /// </para> - /// </summary> - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public sealed class ServiceConfiguratorAttribute : Attribute - { } - - /// <summary> - /// A safe delegate that matches the signature of the <see cref="ServiceConfiguratorAttribute"/> - /// method exposed - /// </summary> - /// <param name="sender"></param> - public delegate void ServiceConfigurator(IServiceContainer sender); -} diff --git a/lib/Plugins/src/Web/IWebPlugin.cs b/lib/Plugins/src/ExportFlags.cs index ed080d0..25c1f9f 100644 --- a/lib/Plugins/src/Web/IWebPlugin.cs +++ b/lib/Plugins/src/ExportFlags.cs @@ -1,11 +1,11 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins -* File: IWebPlugin.cs +* File: ExportFlags.cs * -* IWebPlugin.cs is part of VNLib.Plugins which is part of the larger +* 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 @@ -22,24 +22,19 @@ * along with VNLib.Plugins. If not, see http://www.gnu.org/licenses/. */ -using System.Collections.Generic; +using System; namespace VNLib.Plugins { /// <summary> - /// Represents a plugin that is expected to perform web application based operations + /// Service export flags /// </summary> - public interface IWebPlugin : IPlugin + [Flags] + public enum ExportFlags { /// <summary> - /// Returns all endpoints within the plugin to load into the current root + /// No flags /// </summary> - /// <returns>An enumeration of endpoints to load</returns> - /// <remarks> - /// Lifecycle: Results returned from this method should be consistant (although its only - /// likely to be called once) anytime after the <see cref="IPlugin.Load"/> method, and undefined - /// after the <see cref="IPlugin.Unload"/> method is called. - /// </remarks> - IEnumerable<IEndpoint> GetEndpoints(); + 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 /// </summary> void Unload(); + + /// <summary> + /// Called directly after the plugin is loaded to allow for service + /// publishing for the host application to use + /// <para> + /// NOTE: A service pool is shared for each assembly, if there are multiple + /// plugins per assembly, they will share the same service pool. + /// </para> + /// </summary> + /// <param name="pool">The service publisher instance</param> + 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 +{ + /// <summary> + /// Represents a type that exposes services to the loading application + /// </summary> + public interface IPluginServicePool + { + /// <summary> + /// Publishes a generic service to the service pool + /// </summary> + /// <param name="serviceType">The <see cref="Type"/> to expose to the pool for searching</param> + /// <param name="service">The service instance to publish</param> + /// <param name="flags">Optional flags to pass during export</param> + void ExportService(Type serviceType, object service, ExportFlags flags = ExportFlags.None); + } +}
\ No newline at end of file |