From 44a05ac3854d6cd0fa65d8ffc0f6efe7abfc87ad Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 18 Oct 2023 00:55:12 -0400 Subject: Essentials, plugins, and http core experimental updates --- .../src/Construction/HttpServiceStackBuilder.cs | 9 ++- .../src/Construction/IVirtualHostHooks.cs | 16 +++-- .../src/Construction/SsBuilderExtensions.cs | 4 +- .../src/HttpServiceStack.cs | 2 +- .../src/IPluginInitializer.cs | 3 - .../src/PluginExtensions.cs | 6 +- .../src/PluginLoadEventListener.cs | 80 ++++++++++++++++++++++ .../src/PluginManager.cs | 48 ++----------- .../src/PluginStackInitializer.cs | 36 ++++++---- 9 files changed, 132 insertions(+), 72 deletions(-) create mode 100644 lib/Plugins.Essentials.ServiceStack/src/PluginLoadEventListener.cs (limited to 'lib/Plugins.Essentials.ServiceStack') diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs index c4d602b..91184bd 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs @@ -150,6 +150,11 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction servers.AddLast(server); } + return new(servers, sd, GetPluginStack(sd)); + } + + private PluginStackInitializer GetPluginStack(ServiceDomain domain) + { //Always init manual array manualPlugins ??= Array.Empty(); @@ -160,9 +165,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction plugins ??= new EmptyPluginStack(); #pragma warning restore CA2000 // Dispose objects before losing scope - IPluginInitializer init = new PluginStackInitializer(plugins, manualPlugins); - - return new(servers, sd, init); + return new (domain.GetListener(), plugins, manualPlugins); } /* diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/IVirtualHostHooks.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/IVirtualHostHooks.cs index 6691d6b..acfc6e2 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/Construction/IVirtualHostHooks.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/IVirtualHostHooks.cs @@ -23,7 +23,6 @@ */ using System.Net; -using System.Threading.Tasks; using VNLib.Net.Http; @@ -52,7 +51,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// When an error occurs and is handled by the library, this event is invoked /// /// - /// NOTE: This function must be thread-safe! + /// NOTE: This function must be thread-safe! And should not throw exceptions /// /// /// The error code that was created during processing @@ -64,14 +63,19 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// For pre-processing a request entity before all endpoint lookups are performed /// /// The http entity to process - /// The results to return to the file processor, or null of the entity requires further processing - ValueTask PreProcessEntityAsync(HttpEntity entity); + /// The results to return to the file processor, or if processing should continue + /// + void PreProcessEntityAsync(HttpEntity entity, out FileProcessArgs args); /// - /// Allows for post processing of a selected for the given entity + /// Allows for post processing of a selected for the given entity. + /// + /// This method may mutate the argument referrence to change the + /// the routine that will be used to process the file. + /// /// /// The http entity to process /// The selected file processing routine for the given request - void PostProcessFile(HttpEntity entity, in FileProcessArgs chosenRoutine); + void PostProcessFile(HttpEntity entity, ref FileProcessArgs chosenRoutine); } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs index d518218..54b7ff2 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs @@ -316,10 +316,10 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction public override bool ErrorHandler(HttpStatusCode errorCode, IHttpEvent entity) => Hooks.ErrorHandler(errorCode, entity); /// - public override void PostProcessFile(HttpEntity entity, in FileProcessArgs chosenRoutine) => Hooks.PostProcessFile(entity, chosenRoutine); + public override void PreProcessEntity(HttpEntity entity, out FileProcessArgs preProcArgs) => Hooks.PreProcessEntityAsync(entity, out preProcArgs); /// - public override ValueTask PreProcessEntityAsync(HttpEntity entity) => Hooks.PreProcessEntityAsync(entity); + public override void PostProcessEntity(HttpEntity entity, ref FileProcessArgs chosenRoutine) => Hooks.PostProcessFile(entity, ref chosenRoutine); /// public override string TranslateResourcePath(string requestPath) => Hooks.TranslateResourcePath(requestPath); diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs index 7cd5ac4..16bc1a0 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs @@ -65,7 +65,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack { _servers = servers; _serviceDomain = serviceDomain; - _plugins = new(serviceDomain, plugins); + _plugins = new(plugins); WaitForAllTask = Task.CompletedTask; } diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs index ac91f45..a9aa103 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs @@ -24,14 +24,11 @@ using VNLib.Utils.Logging; -using VNLib.Plugins.Runtime; namespace VNLib.Plugins.Essentials.ServiceStack { internal interface IPluginInitializer { - void PrepareStack(IPluginEventListener listener); - IManagedPlugin[] InitializePluginStack(ILogProvider eventLogger); void UnloadPlugins(); diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs index 010039d..d93df6d 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginExtensions.cs @@ -25,8 +25,8 @@ using System.Linq; using System.Collections.Generic; -using VNLib.Plugins.Runtime; using VNLib.Utils.Logging; +using VNLib.Plugins.Runtime; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -47,7 +47,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// /// - internal static IEnumerable GetOnlyWebPlugins(this PluginController controller) => controller.Plugins.Where(p => p.Plugin is IWebPlugin); + internal static IEnumerable GetOnlyWebPlugins(this PluginController controller) => controller.Plugins.Where(static p => p.Plugin is IWebPlugin); /// /// Loads all plugins that implement interface into the @@ -56,5 +56,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// /// A log provider for writing loading logs to public static void LoadPlugins(this HttpServiceStack stack, ILogProvider logProvider) => (stack.PluginManager as PluginManager)!.LoadPlugins(logProvider); + + internal static PluginLoadEventListener GetListener(this ServiceDomain domain) => new(domain); } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginLoadEventListener.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadEventListener.cs new file mode 100644 index 0000000..0eaa3a8 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadEventListener.cs @@ -0,0 +1,80 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: PluginLoadEventListener.cs +* +* PluginLoadEventListener.cs is part of VNLib.Plugins.Essentials.ServiceStack which +* is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 2 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.ServiceStack is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + + +using System.Linq; + +using VNLib.Utils.Extensions; +using VNLib.Plugins.Runtime; + +namespace VNLib.Plugins.Essentials.ServiceStack +{ + internal sealed record class PluginLoadEventListener(ServiceDomain Domain) : IPluginEventListener + { + /// + void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state) => OnPluginLoaded((state as IManagedPlugin)!); + + /// + void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state) => OnPluginUnloaded((state as IManagedPlugin)!); + + /// + /// Called when a plugin has been successfully loaded and + /// should be put into service + /// + /// The plugin that was loaded + public void OnPluginLoaded(IManagedPlugin plugin) + { + //Run onload method before invoking other handlers + plugin.OnPluginLoaded(); + + //Get event listeners at event time because deps may be modified by the domain + ServiceGroup[] deps = Domain.ServiceGroups.Select(static d => d).ToArray(); + + //run onload method + deps.TryForeach(d => d.OnPluginLoaded(plugin)); + } + + /// + /// Called when a plugin is about to be unloaded and should + /// be removed from service. + /// + /// The plugin instance to unload + public void OnPluginUnloaded(IManagedPlugin plugin) + { + try + { + //Get event listeners at event time because deps may be modified by the domain + ServiceGroup[] deps = Domain.ServiceGroups.Select(static d => d).ToArray(); + + //Run unloaded method + deps.TryForeach(d => d.OnPluginUnloaded(plugin)); + } + finally + { + //always unload the plugin wrapper + plugin.OnPluginUnloaded(); + } + } + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs index 2f57367..ba3b91a 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs @@ -24,13 +24,10 @@ using System; -using System.Linq; using System.Collections.Generic; using VNLib.Utils; using VNLib.Utils.Logging; -using VNLib.Utils.Extensions; -using VNLib.Plugins.Runtime; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -39,9 +36,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// A sealed type that manages the plugin interaction layer. Manages the lifetime of plugin /// instances, exposes controls, and relays stateful plugin events. /// - internal sealed class PluginManager : VnDisposeable, IHttpPluginManager, IPluginEventListener - { - private readonly ServiceDomain _dependents; + internal sealed class PluginManager : VnDisposeable, IHttpPluginManager + { private readonly IPluginInitializer _stack; /// @@ -51,9 +47,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack private IManagedPlugin[] _loadedPlugins; - public PluginManager(ServiceDomain dependents, IPluginInitializer stack) + public PluginManager(IPluginInitializer stack) { - _dependents = dependents; _stack = stack; _loadedPlugins = Array.Empty(); } @@ -62,11 +57,13 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// Configures the manager to capture and manage plugins within a plugin stack /// /// + /// + /// public void LoadPlugins(ILogProvider debugLog) { _ = _stack ?? throw new InvalidOperationException("Plugin stack has not been set."); - _stack.PrepareStack(this); + Check(); //Initialize the plugin stack and store the loaded plugins _loadedPlugins = _stack.InitializePluginStack(debugLog); @@ -124,38 +121,5 @@ namespace VNLib.Plugins.Essentials.ServiceStack //Dispose the plugin stack _stack.Dispose(); } - - void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state) - { - IManagedPlugin mp = (state as IManagedPlugin)!; - - //Run onload method before invoking other handlers - mp.OnPluginLoaded(); - - //Get event listeners at event time because deps may be modified by the domain - ServiceGroup[] deps = _dependents.ServiceGroups.Select(static d => d).ToArray(); - - //run onload method - deps.TryForeach(d => d.OnPluginLoaded(mp)); - } - - void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state) - { - IManagedPlugin plugin = (state as IManagedPlugin)!; - - try - { - //Get event listeners at event time because deps may be modified by the domain - ServiceGroup[] deps = _dependents.ServiceGroups.Select(static d => d).ToArray(); - - //Run unloaded method - deps.TryForeach(d => d.OnPluginUnloaded(plugin)); - } - finally - { - //always unload the plugin wrapper - plugin.OnPluginUnloaded(); - } - } } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs index 6ccd862..8a4e801 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs @@ -40,13 +40,12 @@ using VNLib.Plugins.Attributes; namespace VNLib.Plugins.Essentials.ServiceStack { - internal sealed record class PluginStackInitializer(IPluginStack Stack, IManualPlugin[] ManualPlugins) : IPluginInitializer + internal sealed record class PluginStackInitializer(PluginLoadEventListener Listener, IPluginStack Stack, IManualPlugin[] ManualPlugins) : IPluginInitializer { private readonly LinkedList _managedPlugins = new(); private readonly LinkedList _manualPlugins = new(); - - /// - public void PrepareStack(IPluginEventListener listener) + + private void PrepareStack() { /* * Since we own the plugin stack, it is safe to build it here. @@ -62,7 +61,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack Array.ForEach(wrapper, p => _managedPlugins.AddLast(p)); //Register for all plugins and pass the plugin instance as the state object - Array.ForEach(wrapper, p => p.Plugin.Controller.Register(listener, p)); + Array.ForEach(wrapper, p => p.Plugin.Controller.Register(Listener, p)); //Add manual plugins to list of managed plugins Array.ForEach(ManualPlugins, p => _manualPlugins.AddLast(new ManualPluginWrapper(p))); @@ -71,6 +70,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// public IManagedPlugin[] InitializePluginStack(ILogProvider debugLog) { + //Prepare the plugin stack before initializing + PrepareStack(); + //single thread initialziation LinkedList _loadedPlugins = new(); @@ -96,6 +98,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack public void UnloadPlugins() { Stack.UnloadAll(); + + //Unload manual plugins in listener + _managedPlugins.TryForeach(mp => Listener.OnPluginUnloaded(mp)); _manualPlugins.TryForeach(static mp => mp.Unload()); } @@ -104,9 +109,13 @@ namespace VNLib.Plugins.Essentials.ServiceStack { Stack.ReloadAll(); - //Reload manual plugins + //Unload manual plugins in listener, then call the unload method + _managedPlugins.TryForeach(mp => Listener.OnPluginUnloaded(mp)); _manualPlugins.TryForeach(static mp => mp.Unload()); + + //Load, then invoke on-loaded events _manualPlugins.TryForeach(static mp => mp.Load()); + _managedPlugins.TryForeach(mp => Listener.OnPluginLoaded(mp)); } /// @@ -155,7 +164,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack return false; } - private static void LoadPlugin(IManagedPlugin plugin, ILogProvider debugLog) + private void LoadPlugin(IManagedPlugin plugin, ILogProvider debugLog) { Stopwatch sw = new(); try @@ -170,6 +179,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack else if (plugin is ManualPluginWrapper mpw) { mpw.Load(); + //Call the on-load event in listener explicitly + Listener.OnPluginLoaded(plugin); } else { @@ -313,13 +324,12 @@ namespace VNLib.Plugins.Essentials.ServiceStack return false; } + + void IManagedPlugin.OnPluginLoaded() + { } - - /* - * SHOULD NEVER BE CALLED - */ - void IManagedPlugin.OnPluginLoaded() => throw new NotImplementedException(); - void IManagedPlugin.OnPluginUnloaded() => throw new NotImplementedException(); + void IManagedPlugin.OnPluginUnloaded() + { } /// public override string ToString() => Plugin.Name; -- cgit