aboutsummaryrefslogtreecommitdiff
path: root/lib/Plugins.Essentials.ServiceStack
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-03-09 01:48:28 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-03-09 01:48:28 -0500
commit5ddef0fcb742e77b99a0e17015d2eea0a1d4131a (patch)
treec1c88284b11b70d9f373215d8d54e8a168cc5700 /lib/Plugins.Essentials.ServiceStack
parentdab71d5597fdfbe71f6ac310a240835716e952a5 (diff)
Omega cache, session, and account provider complete overhaul
Diffstat (limited to 'lib/Plugins.Essentials.ServiceStack')
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs33
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs6
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs28
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs54
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs32
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IUnloadableServiceProvider.cs42
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs202
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs62
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs240
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs284
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs76
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/VNLib.Plugins.Essentials.ServiceStack.csproj1
12 files changed, 736 insertions, 324 deletions
diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs
index 5800955..45282b3 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.ServiceStack
@@ -22,15 +22,20 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
using VNLib.Utils;
using VNLib.Net.Http;
namespace VNLib.Plugins.Essentials.ServiceStack
{
/// <summary>
- /// The service domain controller that manages all
- /// servers for an application based on a
- /// <see cref="ServiceDomain"/>
+ /// An HTTP servicing stack that manages a collection of HTTP servers
+ /// their service domain
/// </summary>
public sealed class HttpServiceStack : VnDisposeable
{
@@ -48,7 +53,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// <summary>
/// The service domain's plugin controller
/// </summary>
- public IPluginController PluginController => _serviceDomain;
+ public IPluginManager PluginManager => _serviceDomain.PluginManager;
/// <summary>
/// Initializes a new <see cref="HttpServiceStack"/> that will
@@ -74,14 +79,14 @@ namespace VNLib.Plugins.Essentials.ServiceStack
//Init new linked cts to stop all servers if cancelled
_cts = CancellationTokenSource.CreateLinkedTokenSource(parentToken);
- LinkedList<Task> runners = new();
+ //Start all servers
+ Task[] runners = _servers.Select(s => s.Start(_cts.Token)).ToArray();
- foreach(HttpServer server in _servers)
- {
- //Start servers and add run task to list
- Task run = server.Start(_cts.Token);
- runners.AddLast(run);
- }
+ //Check for failed startups
+ Task? firstFault = runners.Where(static t => t.IsFaulted).FirstOrDefault();
+
+ //Raise first exception
+ firstFault?.GetAwaiter().GetResult();
//Task that waits for all to exit then cleans up
WaitForAllTask = Task.WhenAll(runners)
@@ -96,6 +101,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// <returns>The task that completes when</returns>
public Task StopAndWaitAsync()
{
+ Check();
+
_cts?.Cancel();
return WaitForAllTask;
}
@@ -103,7 +110,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
private void OnAllServerExit(Task allExit)
{
//Unload the hosts
- _serviceDomain.UnloadAll();
+ _serviceDomain.TearDown();
}
///<inheritdoc/>
diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs
index bb6e96f..0b75031 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStackBuilder.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.ServiceStack
@@ -22,6 +22,10 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
using VNLib.Net.Http;
namespace VNLib.Plugins.Essentials.ServiceStack
diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs
index 0871fdc..fb9c340 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/IPluginController.cs
@@ -1,12 +1,12 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.ServiceStack
-* File: IPluginController.cs
+* File: IPluginManager.cs
*
-* IPluginController.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger
-* VNLib collection of libraries and utilities.
+* IPluginManager.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
@@ -22,7 +22,9 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-using System.Text.Json;
+using System;
+using System.Threading.Tasks;
+using System.Collections.Generic;
using VNLib.Utils.Logging;
@@ -32,16 +34,21 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// Represents a live plugin controller that manages all
/// plugins loaded in a <see cref="ServiceDomain"/>
/// </summary>
- public interface IPluginController
+ public interface IPluginManager
{
/// <summary>
+ /// The the plugins managed by this <see cref="IPluginManager"/>
+ /// </summary>
+ public IEnumerable<IManagedPlugin> Plugins { get; }
+
+ /// <summary>
/// Loads all plugins specified by the host config to the service manager,
/// or attempts to load plugins by the default
/// </summary>
/// <param name="config">The configuration instance to pass to plugins</param>
/// <param name="appLog">A log provider to write message and errors to</param>
/// <returns>A task that resolves when all plugins are loaded</returns>
- Task LoadPlugins(JsonDocument config, ILogProvider appLog);
+ Task LoadPluginsAsync(PluginLoadConfiguration config, ILogProvider appLog);
/// <summary>
/// Sends a message to a plugin identified by it's name.
@@ -61,11 +68,8 @@ namespace VNLib.Plugins.Essentials.ServiceStack
void ForceReloadAllPlugins();
/// <summary>
- /// Unloads all service groups, removes them, and unloads all
- /// loaded plugins
+ /// Unloads all loaded plugins and calls thier event handlers
/// </summary>
- /// <exception cref="AggregateException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- void UnloadAll();
+ void UnloadPlugins();
}
}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs
new file mode 100644
index 0000000..2e686ee
--- /dev/null
+++ b/lib/Plugins.Essentials.ServiceStack/src/IPluginWrapper.cs
@@ -0,0 +1,54 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.ServiceStack
+* File: IPluginWrapper.cs
+*
+* IPluginWrapper.cs is part of VNLib.Plugins.Essentials.ServiceStack which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials.ServiceStack is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 2 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Essentials.ServiceStack is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using VNLib.Plugins.Runtime;
+
+namespace VNLib.Plugins.Essentials.ServiceStack
+{
+
+ /// <summary>
+ /// Represents a plugin managed by a <see cref="IPluginManager"/> that includes dynamically loaded plugins
+ /// </summary>
+ public interface IManagedPlugin
+ {
+ /// <summary>
+ /// Exposes the internal <see cref="PluginController"/> for the loaded plugin
+ /// </summary>
+ PluginController Controller { get; }
+
+ /// <summary>
+ /// The file path to the loaded plugin
+ /// </summary>
+ string PluginPath { get; }
+
+ /// <summary>
+ /// The exposed services the inernal plugin provides
+ /// </summary>
+ /// <remarks>
+ /// WARNING: Services exposed by the plugin will abide by the plugin lifecycle, so consumers
+ /// must listen for plugin load/unload events to respect lifecycles properly.
+ /// </remarks>
+ IUnloadableServiceProvider Services { get; }
+ }
+}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs b/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs
index 0c8d6c1..bb4f65f 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/IServiceHost.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.ServiceStack
@@ -22,21 +22,43 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using VNLib.Net.Http;
+
namespace VNLib.Plugins.Essentials.ServiceStack
{
/// <summary>
- /// Represents a host that exposes a processor for host events
+ /// Represents an HTTP service host which provides information required
+ /// for HttpServer routing and the <see cref="IWebRoot"/> for proccessing
+ /// incomming connections
/// </summary>
public interface IServiceHost
{
/// <summary>
- /// The <see cref="EventProcessor"/> to process
- /// incoming HTTP connections
+ /// The <see cref="IWebRoot"/> that handles HTTP connection
+ /// processing.
/// </summary>
- EventProcessor Processor { get; }
+ IWebRoot Processor { get; }
+
/// <summary>
/// The host's transport infomration
/// </summary>
IHostTransportInfo TransportInfo { get; }
+
+ /// <summary>
+ /// Called when a plugin is loaded and is endpoints are extracted
+ /// to be placed into service.
+ /// </summary>
+ /// <param name="plugin">The loaded plugin ready to be attached</param>
+ /// <param name="endpoints">The dynamic endpoints of a loading plugin</param>
+ void OnRuntimeServiceAttach(IManagedPlugin plugin, IEndpoint[] endpoints);
+
+ /// <summary>
+ /// Called when a <see cref="ServiceDomain"/>'s <see cref="IPluginManager"/>
+ /// unloads a given plugin, and its originally discovered endpoints
+ /// </summary>
+ /// <param name="plugin">The unloading plugin to detach</param>
+ /// <param name="endpoints">The endpoints of the unloading plugin to remove from service</param>
+ void OnRuntimeServiceDetach(IManagedPlugin plugin, IEndpoint[] endpoints);
+
}
}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/IUnloadableServiceProvider.cs b/lib/Plugins.Essentials.ServiceStack/src/IUnloadableServiceProvider.cs
new file mode 100644
index 0000000..fa334bd
--- /dev/null
+++ b/lib/Plugins.Essentials.ServiceStack/src/IUnloadableServiceProvider.cs
@@ -0,0 +1,42 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.ServiceStack
+* File: IUnloadableServiceProvider.cs
+*
+* IUnloadableServiceProvider.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;
+using System.Threading;
+
+namespace VNLib.Plugins.Essentials.ServiceStack
+{
+ /// <summary>
+ /// A <see cref="IServiceProvider"/> that may be unloaded when the
+ /// assembly that is sharing the types are being disposed.
+ /// </summary>
+ public interface IUnloadableServiceProvider : IServiceProvider
+ {
+ /// <summary>
+ /// A token that is set cancelled state when the service provider
+ /// is unloaded.
+ /// </summary>
+ CancellationToken UnloadToken { get; }
+ }
+}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs
new file mode 100644
index 0000000..596ea83
--- /dev/null
+++ b/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs
@@ -0,0 +1,202 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.ServiceStack
+* File: ManagedPlugin.cs
+*
+* ManagedPlugin.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;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Reflection;
+using System.Threading.Tasks;
+using System.ComponentModel.Design;
+
+using VNLib.Utils;
+using VNLib.Plugins.Runtime;
+using VNLib.Plugins.Attributes;
+
+namespace VNLib.Plugins.Essentials.ServiceStack
+{
+
+ internal sealed class ManagedPlugin : VnDisposeable, IPluginEventListener, IManagedPlugin
+ {
+ private readonly IPluginEventListener _serviceDomainListener;
+ private readonly RuntimePluginLoader _plugin;
+
+ private UnloadableServiceContainer? _services;
+
+ public ManagedPlugin(string pluginPath, PluginLoadConfiguration config, IPluginEventListener listener)
+ {
+ PluginPath = pluginPath;
+
+ //configure the loader
+ _plugin = new(pluginPath, config.HostConfig, config.PluginErrorLog, config.HotReload, config.HotReload);
+
+ //Register listener before loading occurs
+ _plugin.Controller.Register(this, this);
+
+ //Store listener to raise events
+ _serviceDomainListener = listener;
+ }
+
+ ///<inheritdoc/>
+ public string PluginPath { get; }
+
+ ///<inheritdoc/>
+ public IUnloadableServiceProvider Services
+ {
+ get
+ {
+ Check();
+ return _services!;
+ }
+ }
+
+ ///<inheritdoc/>
+ public PluginController Controller
+ {
+ get
+ {
+ Check();
+ return _plugin.Controller;
+ }
+ }
+
+ internal string PluginFileName => Path.GetFileName(PluginPath);
+
+ internal Task InitializePluginsAsync()
+ {
+ Check();
+ return _plugin.InitializeController();
+ }
+
+ internal void LoadPlugins()
+ {
+ Check();
+ _plugin.LoadPlugins();
+ }
+
+ /*
+ * Automatically called after the plugin has successfully loaded
+ * by event handlers below
+ */
+ private void ConfigureServices()
+ {
+ //If the service container is defined, dispose
+ _services?.Dispose();
+
+ //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);
+ }
+ }
+
+ internal void ReloadPlugins()
+ {
+ Check();
+ _plugin.ReloadPlugins();
+ }
+
+ internal void UnloadPlugins()
+ {
+ Check();
+
+ //unload plugins
+ _plugin.UnloadAll();
+
+ //Services will be cleaned up by the unload event
+ }
+
+ void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state)
+ {
+ //Initialize services after load, before passing event
+ ConfigureServices();
+
+ //Propagate event
+ _serviceDomainListener.OnPluginLoaded(controller, state);
+ }
+
+ void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state)
+ {
+ //Cleanup services no longer in use. Plugin is still valid until this method returns
+ using (_services)
+ {
+ //Propagate event
+ _serviceDomainListener.OnPluginUnloaded(controller, state);
+
+ //signal service cancel before disposing
+ _services?.SignalUnload();
+ }
+ //Remove ref to services
+ _services = null;
+ }
+
+ protected override void Free()
+ {
+ //Dispose services
+ _services?.Dispose();
+ //Unregister the listener to cleanup resources
+ _plugin.Controller.Unregister(this);
+ //Dispose loader
+ _plugin.Dispose();
+ }
+
+
+ private sealed class UnloadableServiceContainer : ServiceContainer, IUnloadableServiceProvider
+ {
+ private readonly CancellationTokenSource _cts;
+
+ public UnloadableServiceContainer() : base()
+ {
+ _cts = new();
+ }
+
+ ///<inheritdoc/>
+ CancellationToken IUnloadableServiceProvider.UnloadToken => _cts.Token;
+
+ /// <summary>
+ /// Signals to listensers that the service container will be unloading
+ /// </summary>
+ internal void SignalUnload() => _cts.Cancel();
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ _cts.Dispose();
+ }
+ }
+ }
+}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs
new file mode 100644
index 0000000..4974e71
--- /dev/null
+++ b/lib/Plugins.Essentials.ServiceStack/src/PluginLoadConfiguration.cs
@@ -0,0 +1,62 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.ServiceStack
+* File: PluginLoadConfiguration.cs
+*
+* PluginLoadConfiguration.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.Text.Json;
+
+using VNLib.Utils.Logging;
+using VNLib.Plugins.Runtime;
+
+
+namespace VNLib.Plugins.Essentials.ServiceStack
+{
+ /// <summary>
+ /// Plugin loading configuration variables
+ /// </summary>
+ public readonly record struct PluginLoadConfiguration
+ {
+ /// <summary>
+ /// The directory containing the dynamic plugin assemblies to load
+ /// </summary>
+ public readonly string PluginDir { get; init; }
+
+ /// <summary>
+ /// A value that indicates if the internal <see cref="PluginController"/>
+ /// allows for hot-reload/unloadable plugin assemblies.
+ /// </summary>
+ public readonly bool HotReload { get; init; }
+
+ /// <summary>
+ /// The optional host configuration file to merge with plugin config
+ /// to pass to the loading plugin.
+ /// </summary>
+ public readonly JsonDocument? HostConfig { get; init; }
+
+ /// <summary>
+ /// Passed to the underlying <see cref="RuntimePluginLoader"/>
+ /// holding plugins
+ /// </summary>
+ public readonly ILogProvider? PluginErrorLog { get; init; }
+ }
+}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs
new file mode 100644
index 0000000..cdcf7ba
--- /dev/null
+++ b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs
@@ -0,0 +1,240 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials.ServiceStack
+* File: PluginManager.cs
+*
+* PluginManager.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;
+using System.IO;
+using System.Linq;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using VNLib.Utils;
+using VNLib.Utils.IO;
+using VNLib.Utils.Logging;
+using VNLib.Utils.Extensions;
+using VNLib.Plugins.Runtime;
+
+namespace VNLib.Plugins.Essentials.ServiceStack
+{
+
+ /// <summary>
+ /// A sealed type that manages the plugin interaction layer. Manages the lifetime of plugin
+ /// instances, exposes controls, and relays stateful plugin events.
+ /// </summary>
+ internal sealed class PluginManager : VnDisposeable, IPluginManager, IPluginEventListener
+ {
+ private const string PLUGIN_FILE_EXTENSION = ".dll";
+
+ private readonly List<ManagedPlugin> _plugins;
+ private readonly IReadOnlyCollection<ServiceGroup> _dependents;
+
+
+ private IEnumerable<LivePlugin> _livePlugins => _plugins.SelectMany(static p => p.Controller.Plugins);
+
+ /// <summary>
+ /// The collection of internal controllers
+ /// </summary>
+ public IEnumerable<IManagedPlugin> Plugins => _plugins;
+
+ public PluginManager(IReadOnlyCollection<ServiceGroup> dependents)
+ {
+ _plugins = new();
+ _dependents = dependents;
+ }
+
+ /// <inheritdoc/>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public Task LoadPluginsAsync(PluginLoadConfiguration config, ILogProvider appLog)
+ {
+ Check();
+
+ //Load all virtual file assemblies withing the plugin folder
+ DirectoryInfo dir = new(config.PluginDir);
+
+ if (!dir.Exists)
+ {
+ appLog.Warn("Plugin directory {dir} does not exist. No plugins were loaded", config.PluginDir);
+ return Task.CompletedTask;
+ }
+
+ appLog.Information("Loading plugins. Hot-reload: {en}", config.HotReload);
+
+ //Enumerate all dll files within this dir
+ IEnumerable<DirectoryInfo> dirs = dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly);
+
+ //Select only dirs with a dll that is named after the directory name
+ IEnumerable<string> pluginPaths = GetPluginPaths(dirs);
+
+ IEnumerable<string> pluginFileNames = pluginPaths.Select(static s => $"{Path.GetFileName(s)}\n");
+
+ appLog.Debug("Found plugin files: \n{files}", string.Concat(pluginFileNames));
+
+ //Initialze plugin managers
+ ManagedPlugin[] wrappers = pluginPaths.Select(pw => new ManagedPlugin(pw, config, this)).ToArray();
+
+ //Add to loaded plugins
+ _plugins.AddRange(wrappers);
+
+ //Load plugins
+ return InitiailzeAndLoadAsync(appLog);
+ }
+
+ private static IEnumerable<string> GetPluginPaths(IEnumerable<DirectoryInfo> dirs)
+ {
+ //Select only dirs with a dll that is named after the directory name
+ return dirs.Where(static pdir =>
+ {
+ string compined = Path.Combine(pdir.FullName, pdir.Name);
+ string FilePath = string.Concat(compined, PLUGIN_FILE_EXTENSION);
+ return FileOperations.FileExists(FilePath);
+ })
+ //Return the name of the dll file to import
+ .Select(static pdir =>
+ {
+ string compined = Path.Combine(pdir.FullName, pdir.Name);
+ return string.Concat(compined, PLUGIN_FILE_EXTENSION);
+ });
+ }
+
+ private async Task InitiailzeAndLoadAsync(ILogProvider debugLog)
+ {
+ //Load all async
+ Task[] initAll = _plugins.Select(p => InitializePlugin(p, debugLog)).ToArray();
+
+ //Wait for initalization
+ await Task.WhenAll(initAll).ConfigureAwait(false);
+
+ //Load stage, load all multithreaded
+ Parallel.ForEach(_plugins, p => LoadPlugin(p, debugLog));
+
+ debugLog.Information("Plugin loading completed");
+ }
+
+ private async Task InitializePlugin(ManagedPlugin plugin, ILogProvider debugLog)
+ {
+ try
+ {
+ //Load wrapper
+ await plugin.InitializePluginsAsync().ConfigureAwait(true);
+ }
+ catch (Exception ex)
+ {
+ debugLog.Error(ex, $"Exception raised during initialzation of {plugin.PluginFileName}. It has been removed from the collection\n{ex}");
+
+ //Remove the plugin from the list while locking it
+ lock (_plugins)
+ {
+ _plugins.Remove(plugin);
+ }
+
+ //Dispose the plugin
+ plugin.Dispose();
+ }
+ }
+
+ private static void LoadPlugin(ManagedPlugin plugin, ILogProvider debugLog)
+ {
+ Stopwatch sw = new();
+ try
+ {
+ sw.Start();
+
+ //Load wrapper
+ plugin.LoadPlugins();
+
+ sw.Stop();
+
+ debugLog.Verbose("Loaded {pl} in {tm} ms", plugin.PluginFileName, sw.ElapsedMilliseconds);
+ }
+ catch (Exception ex)
+ {
+ debugLog.Error(ex, $"Exception raised during loading {plugin.PluginFileName}. Failed to load plugin \n{ex}");
+ }
+ finally
+ {
+ sw.Stop();
+ }
+ }
+
+ /// <inheritdoc/>
+ public bool SendCommandToPlugin(string pluginName, string message, StringComparison nameComparison = StringComparison.Ordinal)
+ {
+ Check();
+
+ //Find the single plugin by its name
+ LivePlugin? pl = _livePlugins.Where(p => pluginName.Equals(p.PluginName, nameComparison)).SingleOrDefault();
+
+ //Send the command
+ return pl?.SendConsoleMessage(message) ?? false;
+ }
+
+ /// <inheritdoc/>
+ public void ForceReloadAllPlugins()
+ {
+ //Reload all plugin managers
+ _plugins.TryForeach(static p => p.ReloadPlugins());
+ }
+
+ /// <inheritdoc/>
+ public void UnloadPlugins()
+ {
+ //Unload all plugin controllers
+ _plugins.TryForeach(static p => p.UnloadPlugins());
+
+ /*
+ * All plugin instances must be destroyed because the
+ * only way they will be loaded is from their files
+ * again, so they must be released
+ */
+ _plugins.TryForeach(static p => p.Dispose());
+ _plugins.Clear();
+ }
+
+ protected override void Free()
+ {
+ //Cleanup on dispose if unload failed
+ _plugins.TryForeach(static p => p.Dispose());
+ _plugins.Clear();
+ }
+
+ void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state)
+ {
+ //Get event listeners at event time because deps may be modified by the domain
+ ServiceGroup[] deps = _dependents.Select(static d => d).ToArray();
+
+ //run onload method
+ deps.TryForeach(d => d.OnPluginLoaded((IManagedPlugin)state!));
+ }
+
+ void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state)
+ {
+ //Get event listeners at event time because deps may be modified by the domain
+ ServiceGroup[] deps = _dependents.Select(static d => d).ToArray();
+
+ //Run unloaded method
+ deps.TryForeach(d => d.OnPluginUnloaded((IManagedPlugin)state!));
+ }
+ }
+}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs b/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs
index 7b06e70..f0f9559 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/ServiceDomain.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.ServiceStack
@@ -24,39 +24,23 @@
using System;
using System.Net;
-using System.Text.Json;
-using System.Diagnostics;
+using System.Linq;
+using System.Collections.Generic;
using VNLib.Utils;
-using VNLib.Utils.IO;
using VNLib.Utils.Extensions;
-using VNLib.Utils.Logging;
-using VNLib.Plugins.Runtime;
-using VNLib.Plugins.Essentials.Content;
-using VNLib.Plugins.Essentials.Sessions;
namespace VNLib.Plugins.Essentials.ServiceStack
{
+
/// <summary>
/// Represents a domain of services and thier dynamically loaded plugins
/// that will be hosted by an application service stack
/// </summary>
- public sealed class ServiceDomain : VnDisposeable, IPluginController
+ public sealed class ServiceDomain : VnDisposeable
{
- private const string PLUGIN_FILE_EXTENSION = ".dll";
- private const string DEFUALT_PLUGIN_DIR = "/plugins";
- private const string PLUGINS_CONFIG_ELEMENT = "plugins";
-
private readonly LinkedList<ServiceGroup> _serviceGroups;
- private readonly LinkedList<RuntimePluginLoader> _pluginLoaders;
-
- /// <summary>
- /// Enumerates all loaded plugin instances
- /// </summary>
- public IEnumerable<IPlugin> Plugins => _pluginLoaders.SelectMany(static s =>
- s.LivePlugins.Where(static p => p.Plugin != null)
- .Select(static s => s.Plugin!)
- );
+ private readonly PluginManager _plugins;
/// <summary>
/// Gets all service groups loaded in the service manager
@@ -64,12 +48,19 @@ namespace VNLib.Plugins.Essentials.ServiceStack
public IReadOnlyCollection<ServiceGroup> ServiceGroups => _serviceGroups;
/// <summary>
+ /// Gets the internal <see cref="IPluginManager"/> that manages plugins for the entire
+ /// <see cref="ServiceDomain"/>
+ /// </summary>
+ public IPluginManager PluginManager => _plugins;
+
+ /// <summary>
/// Initializes a new empty <see cref="ServiceDomain"/>
/// </summary>
public ServiceDomain()
{
_serviceGroups = new();
- _pluginLoaders = new();
+ //Init plugin manager and pass ref to service group collection
+ _plugins = new PluginManager(_serviceGroups);
}
/// <summary>
@@ -78,8 +69,11 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// </summary>
/// <param name="hostBuilder">The callback method to build virtual hosts</param>
/// <returns>A value that indicates if any virtual hosts were successfully loaded</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
public bool BuildDomain(Action<ICollection<IServiceHost>> hostBuilder)
{
+ Check();
+
//LL to store created hosts
LinkedList<IServiceHost> hosts = new();
@@ -94,8 +88,11 @@ namespace VNLib.Plugins.Essentials.ServiceStack
/// </summary>
/// <param name="hosts">The enumeration of virtual hosts</param>
/// <returns>A value that indicates if any virtual hosts were successfully loaded</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
public bool FromExisting(IEnumerable<IServiceHost> hosts)
{
+ Check();
+
//Get service groups and pass service group list
CreateServiceGroups(_serviceGroups, hosts);
return _serviceGroups.Any();
@@ -111,11 +108,12 @@ namespace VNLib.Plugins.Essentials.ServiceStack
{
IEnumerable<IServiceHost> groupHosts = hosts.Where(host => host.TransportInfo.TransportEndpoint.Equals(iface));
- IServiceHost[]? overlap = groupHosts.Where(vh => groupHosts.Select(static s => s.Processor.Hostname).Count(hostname => vh.Processor.Hostname == hostname) > 1).ToArray();
+ //Find any duplicate hostnames for the same service gorup
+ IServiceHost[] overlap = groupHosts.Where(vh => groupHosts.Select(static s => s.Processor.Hostname).Count(hostname => vh.Processor.Hostname == hostname) > 1).ToArray();
- foreach (IServiceHost vh in overlap)
+ if(overlap.Length > 0)
{
- throw new ArgumentException($"The hostname '{vh.Processor.Hostname}' is already in use by another virtual host");
+ throw new ArgumentException($"The hostname '{overlap.Last().Processor.Hostname}' is already in use by another virtual host");
}
//init new service group around an interface and its roots
@@ -125,235 +123,33 @@ namespace VNLib.Plugins.Essentials.ServiceStack
}
}
- ///<inheritdoc/>
- public Task LoadPlugins(JsonDocument config, ILogProvider appLog)
- {
- if (!config.RootElement.TryGetProperty(PLUGINS_CONFIG_ELEMENT, out JsonElement pluginEl))
- {
- appLog.Information("Plugins element not defined in config, skipping plugin loading");
- return Task.CompletedTask;
- }
-
- //Get the plugin directory, or set to default
- string pluginDir = pluginEl.GetPropString("path") ?? Path.Combine(Directory.GetCurrentDirectory(), DEFUALT_PLUGIN_DIR);
- //Get the hot reload flag
- bool hotReload = pluginEl.TryGetProperty("hot_reload", out JsonElement hrel) && hrel.GetBoolean();
-
- //Load all virtual file assemblies withing the plugin folder
- DirectoryInfo dir = new(pluginDir);
-
- if (!dir.Exists)
- {
- appLog.Warn("Plugin directory {dir} does not exist. No plugins were loaded", pluginDir);
- return Task.CompletedTask;
- }
-
- appLog.Information("Loading plugins. Hot-reload: {en}", hotReload);
-
- //Enumerate all dll files within this dir
- IEnumerable<DirectoryInfo> dirs = dir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly);
-
- //Select only dirs with a dll that is named after the directory name
- IEnumerable<string> pluginPaths = dirs.Where(static pdir =>
- {
- string compined = Path.Combine(pdir.FullName, pdir.Name);
- string FilePath = string.Concat(compined, PLUGIN_FILE_EXTENSION);
- return FileOperations.FileExists(FilePath);
- })
- //Return the name of the dll file to import
- .Select(static pdir =>
- {
- string compined = Path.Combine(pdir.FullName, pdir.Name);
- return string.Concat(compined, PLUGIN_FILE_EXTENSION);
- });
-
- IEnumerable<string> pluginFileNames = pluginPaths.Select(static s => $"{Path.GetFileName(s)}\n");
-
- appLog.Debug("Found plugin files: \n{files}", string.Concat(pluginFileNames));
-
- LinkedList<Task> loading = new();
-
- object listLock = new();
-
- foreach (string pluginPath in pluginPaths)
- {
- async Task Load()
- {
- string pluginName = Path.GetFileName(pluginPath);
-
- RuntimePluginLoader plugin = new(pluginPath, config, appLog, hotReload, hotReload);
- Stopwatch sw = new();
- try
- {
- sw.Start();
-
- await plugin.InitLoaderAsync();
-
- //Listen for reload events to remove and re-add endpoints
- plugin.Reloaded += OnPluginReloaded;
-
- lock (listLock)
- {
- //Add to list
- _pluginLoaders.AddLast(plugin);
- }
-
- sw.Stop();
-
- appLog.Verbose("Loaded {pl} in {tm} ms", pluginName, sw.ElapsedMilliseconds);
- }
- catch (Exception ex)
- {
- appLog.Error(ex, $"Exception raised during loading {pluginName}. Failed to load plugin \n{ex}");
- plugin.Dispose();
- }
- finally
- {
- sw.Stop();
- }
- }
-
- loading.AddLast(Load());
- }
-
- //Continuation to add all initial plugins to the service manager
- void Continuation(Task t)
- {
- appLog.Verbose("Plugins loaded");
-
- //Add inital endpoints for all plugins
- _pluginLoaders.TryForeach(ldr => _serviceGroups.TryForeach(sg => sg.AddOrUpdateEndpointsForPlugin(ldr)));
-
- //Init session provider
- InitSessionProvider();
-
- //Init page router
- InitPageRouter();
- }
-
- //wait for loading to completed
- return Task.WhenAll(loading.ToArray()).ContinueWith(Continuation, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
- }
-
- ///<inheritdoc/>
- public bool SendCommandToPlugin(string pluginName, string message, StringComparison nameComparison = StringComparison.Ordinal)
- {
- Check();
- //Find the single plugin by its name
- LivePlugin? pl = _pluginLoaders.Select(p =>
- p.LivePlugins.Where(lp => pluginName.Equals(lp.PluginName, nameComparison))
- )
- .SelectMany(static lp => lp)
- .SingleOrDefault();
- //Send the command
- return pl?.SendConsoleMessage(message) ?? false;
- }
-
- ///<inheritdoc/>
- public void ForceReloadAllPlugins()
- {
- Check();
- _pluginLoaders.TryForeach(static pl => pl.ReloadPlugin());
- }
-
- ///<inheritdoc/>
- public void UnloadAll()
+ /// <summary>
+ /// Tears down the service domain by unloading all plugins (calling their event handlers)
+ /// and destroying all <see cref="ServiceGroup"/>s. This instance may be rebuilt if this
+ /// method returns successfully.
+ /// </summary>
+ internal void TearDown()
{
Check();
- //Unload service groups before unloading plugins
+ /*
+ * Unloading plugins should trigger the OnPluginUnloading
+ * hook which should cause all dependencies to unload linked
+ * types.
+ */
+ _plugins.UnloadPlugins();
+
+ //Manually cleanup if unload missed data
_serviceGroups.TryForeach(static sg => sg.UnloadAll());
//empty service groups
_serviceGroups.Clear();
-
- //Unload all plugins
- _pluginLoaders.TryForeach(static pl => pl.UnloadAll());
- }
-
- private void OnPluginReloaded(object? plugin, EventArgs empty)
- {
- //Update endpoints for the loader
- RuntimePluginLoader reloaded = (plugin as RuntimePluginLoader)!;
-
- //Update all endpoints for the plugin
- _serviceGroups.TryForeach(sg => sg.AddOrUpdateEndpointsForPlugin(reloaded));
- }
-
- private void InitSessionProvider()
- {
- //Callback to reload provider
- void onSessionProviderReloaded(ISessionProvider old, ISessionProvider current)
- {
- _serviceGroups.TryForeach(sg => sg.UpdateSessionProvider(current));
- }
-
- try
- {
- //get the loader that contains the single session provider
- RuntimePluginLoader? sessionLoader = _pluginLoaders
- .Where(static s => s.ExposesType<ISessionProvider>())
- .SingleOrDefault();
-
- //If session provider has been supplied, load it
- if (sessionLoader != null)
- {
- //Get the session provider from the plugin loader
- ISessionProvider sp = sessionLoader.GetExposedTypeFromPlugin<ISessionProvider>()!;
-
- //Init inital provider
- onSessionProviderReloaded(null!, sp);
-
- //Register reload event
- sessionLoader.RegisterListenerForSingle<ISessionProvider>(onSessionProviderReloaded);
- }
- }
- catch (InvalidOperationException)
- {
- throw new TypeLoadException("More than one session provider plugin was defined in the plugin directory, cannot continue");
- }
- }
-
- private void InitPageRouter()
- {
- //Callback to reload provider
- void onRouterReloaded(IPageRouter old, IPageRouter current)
- {
- _serviceGroups.TryForeach(sg => sg.UpdatePageRouter(current));
- }
-
- try
- {
-
- //get the loader that contains the single page router
- RuntimePluginLoader? routerLoader = _pluginLoaders
- .Where(static s => s.ExposesType<IPageRouter>())
- .SingleOrDefault();
-
- //If router has been supplied, load it
- if (routerLoader != null)
- {
- //Get initial value
- IPageRouter sp = routerLoader.GetExposedTypeFromPlugin<IPageRouter>()!;
-
- //Init inital provider
- onRouterReloaded(null!, sp);
-
- //Register reload event
- routerLoader.RegisterListenerForSingle<IPageRouter>(onRouterReloaded);
- }
- }
- catch (InvalidOperationException)
- {
- throw new TypeLoadException("More than one page router plugin was defined in the plugin directory, cannot continue");
- }
}
+
///<inheritdoc/>
protected override void Free()
{
- //Dispose loaders
- _pluginLoaders.TryForeach(static pl => pl.Dispose());
- _pluginLoaders.Clear();
+ _plugins.Dispose();
_serviceGroups.Clear();
}
}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs
index f57a6f9..2801776 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials.ServiceStack
@@ -23,13 +23,11 @@
*/
using System.Net;
+using System.Linq;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using VNLib.Utils.Extensions;
-using VNLib.Plugins.Runtime;
-using VNLib.Plugins.Essentials.Content;
-using VNLib.Plugins.Essentials.Sessions;
namespace VNLib.Plugins.Essentials.ServiceStack
{
@@ -42,7 +40,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack
public sealed class ServiceGroup
{
private readonly LinkedList<IServiceHost> _vHosts;
- private readonly ConditionalWeakTable<RuntimePluginLoader, IEndpoint[]> _endpointsForPlugins;
+ private readonly ConditionalWeakTable<IManagedPlugin, IEndpoint[]> _endpointsForPlugins;
/// <summary>
/// The <see cref="IPEndPoint"/> transport endpoint for all loaded service hosts
@@ -68,61 +66,43 @@ namespace VNLib.Plugins.Essentials.ServiceStack
}
/// <summary>
- /// Sets the specified page rotuer for all virtual hosts
+ /// Manually detatches runtime services and their loaded endpoints from all
+ /// endpoints.
/// </summary>
- /// <param name="router">The page router to user</param>
- internal void UpdatePageRouter(IPageRouter router) => _vHosts.TryForeach(v => v.Processor.SetPageRouter(router));
- /// <summary>
- /// Sets the specified session provider for all virtual hosts
- /// </summary>
- /// <param name="current">The session provider to use</param>
- internal void UpdateSessionProvider(ISessionProvider current) => _vHosts.TryForeach(v => v.Processor.SetSessionProvider(current));
+ internal void UnloadAll()
+ {
+ //Remove all loaded endpoints
+ _vHosts.TryForeach(v => _endpointsForPlugins.TryForeach(eps => v.OnRuntimeServiceDetach(eps.Key, eps.Value)));
- /// <summary>
- /// Adds or updates all endpoints exported by all plugins
- /// within the specified loader. All endpoints exposed
- /// by a previously loaded instance are removed and all
- /// currently exposed endpoints are added to all virtual
- /// hosts
- /// </summary>
- /// <param name="loader">The plugin loader to get add/update endpoints from</param>
- internal void AddOrUpdateEndpointsForPlugin(RuntimePluginLoader loader)
+ //Clear all hosts
+ _vHosts.Clear();
+ //Clear all endpoints
+ _endpointsForPlugins.Clear();
+ }
+
+ internal void OnPluginLoaded(IManagedPlugin controller)
{
//Get all new endpoints for plugin
- IEndpoint[] newEndpoints = loader.LivePlugins.SelectMany(static pl => pl.Plugin!.GetEndpoints()).ToArray();
-
- //See if
- if(_endpointsForPlugins.TryGetValue(loader, out IEndpoint[]? oldEps))
- {
- //Remove old endpoints
- _vHosts.TryForeach(v => v.Processor.RemoveEndpoint(oldEps));
- }
+ IEndpoint[] newEndpoints = controller.Controller.Plugins.SelectMany(static pl => pl.Plugin!.GetEndpoints()).ToArray();
//Add endpoints to dict
- _endpointsForPlugins.AddOrUpdate(loader, newEndpoints);
+ _endpointsForPlugins.AddOrUpdate(controller, newEndpoints);
//Add endpoints to hosts
- _vHosts.TryForeach(v => v.Processor.AddEndpoint(newEndpoints));
+ _vHosts.TryForeach(v => v.OnRuntimeServiceAttach(controller, newEndpoints));
}
- /// <summary>
- /// Unloads all previously stored endpoints, router, session provider, and
- /// clears all internal data structures
- /// </summary>
- internal void UnloadAll()
+ internal void OnPluginUnloaded(IManagedPlugin controller)
{
- //Remove all loaded endpoints
- _vHosts.TryForeach(v => _endpointsForPlugins.TryForeach(eps => v.Processor.RemoveEndpoint(eps.Value)));
-
- //Remove all routers
- _vHosts.TryForeach(static v => v.Processor.SetPageRouter(null));
- //Remove all session providers
- _vHosts.TryForeach(static v => v.Processor.SetSessionProvider(null));
+ //Get the old endpoints from the controller referrence and remove them
+ if (_endpointsForPlugins.TryGetValue(controller, out IEndpoint[]? oldEps))
+ {
+ //Remove the old endpoints
+ _vHosts.TryForeach(v => v.OnRuntimeServiceDetach(controller, oldEps));
- //Clear all hosts
- _vHosts.Clear();
- //Clear all endpoints
- _endpointsForPlugins.Clear();
+ //remove controller ref
+ _ = _endpointsForPlugins.Remove(controller);
+ }
}
}
}
diff --git a/lib/Plugins.Essentials.ServiceStack/src/VNLib.Plugins.Essentials.ServiceStack.csproj b/lib/Plugins.Essentials.ServiceStack/src/VNLib.Plugins.Essentials.ServiceStack.csproj
index 4918c49..dd7b562 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/VNLib.Plugins.Essentials.ServiceStack.csproj
+++ b/lib/Plugins.Essentials.ServiceStack/src/VNLib.Plugins.Essentials.ServiceStack.csproj
@@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
- <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>VNLib.Plugins.Essentials.ServiceStack</RootNamespace>
<AssemblyName>VNLib.Plugins.Essentials.ServiceStack</AssemblyName>
<Nullable>enable</Nullable>