aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs')
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs141
1 files changed, 79 insertions, 62 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
index 207aeed..fa39b44 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
@@ -31,57 +31,17 @@ using System.Runtime.Loader;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.ComponentModel.Design;
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
+using System.Runtime.ExceptionServices;
using VNLib.Utils.Logging;
+using VNLib.Utils.Resources;
using VNLib.Utils.Extensions;
namespace VNLib.Plugins.Extensions.Loading
{
- /// <summary>
- /// Base class for concrete type loading exceptions. Raised when searching
- /// for a concrete type fails.
- /// </summary>
- public class ConcreteTypeException : TypeLoadException
- {
- public ConcreteTypeException() : base()
- { }
-
- public ConcreteTypeException(string? message) : base(message)
- { }
-
- public ConcreteTypeException(string? message, Exception? innerException) : base(message, innerException)
- { }
- }
-
- /// <summary>
- /// Raised when a concrete type is found but is ambiguous because more than one
- /// type implements the desired abstract type.
- /// </summary>
- public sealed class ConcreteTypeAmbiguousMatchException : ConcreteTypeException
- {
- public ConcreteTypeAmbiguousMatchException(string message) : base(message)
- { }
-
- public ConcreteTypeAmbiguousMatchException(string message, Exception innerException) : base(message, innerException)
- { }
-
- public ConcreteTypeAmbiguousMatchException()
- { }
- }
-
- /// <summary>
- /// The requested concrete type was not found in the assembly
- /// </summary>
- public sealed class ConcreteTypeNotFoundException : ConcreteTypeException
- {
- public ConcreteTypeNotFoundException(string message) : base(message)
- { }
- public ConcreteTypeNotFoundException(string message, Exception innerException) : base(message, innerException)
- { }
- public ConcreteTypeNotFoundException()
- { }
- }
/// <summary>
/// Provides common loading (and unloading when required) extensions for plugins
@@ -102,7 +62,9 @@ namespace VNLib.Plugins.Extensions.Loading
* Plugin local cache used for storing singletons for a plugin instance
*/
private static readonly ConditionalWeakTable<PluginBase, PluginLocalCache> _localCache = new();
-
+ private static readonly ConcurrentDictionary<string, ManagedLibrary> _assemblyCache = new();
+
+
/// <summary>
/// Gets a previously cached service singleton for the desired plugin
/// </summary>
@@ -189,6 +151,10 @@ namespace VNLib.Plugins.Extensions.Loading
//Get the plugin's load context if not explicitly supplied
explictAlc ??= GetPluginLoadContext();
+ if (plugin.IsDebug())
+ {
+ plugin.Log.Verbose("Loading assembly {asm}: from file {file}", assemblyName, asmFile);
+ }
//Load the assembly
return AssemblyLoader<T>.Load(asmFile, explictAlc, plugin.UnloadToken);
}
@@ -375,6 +341,52 @@ namespace VNLib.Plugins.Extensions.Loading
}
/// <summary>
+ /// Creates a new instance of the desired service type from an external assembly and
+ /// caches the loaded assembly so it's never loaded more than once. Managed assembly
+ /// life cycles are managed by the plugin. Instances are treated as services and
+ /// their service hooks will be called like any internal service.
+ /// </summary>
+ /// <typeparam name="T">The service type, may be an interface or abstract type</typeparam>
+ /// <param name="plugin"></param>
+ /// <param name="assemblyDllName">The name of the assembly that contains the desired type to search for</param>
+ /// <param name="search">The directory search method</param>
+ /// <param name="defaultCtx">A <see cref="AssemblyLoadContext"/> to load the assembly into. Defaults to the plugins current ALC</param>
+ /// <returns>A new instance of the desired service type </returns>
+ /// <exception cref="TypeLoadException"></exception>
+ public static T CreateServiceExternal<T>(
+ this PluginBase plugin,
+ string assemblyDllName,
+ SearchOption search = SearchOption.AllDirectories,
+ AssemblyLoadContext? defaultCtx = null
+ )
+ {
+ /*
+ * Get or create the library for the assembly path, but only load it once
+ * Loading it on the plugin will also cause it be cleaned up when the plugin
+ * is unloaded.
+ */
+ ManagedLibrary manLib = _assemblyCache.GetOrAdd(assemblyDllName, (name) => plugin.LoadAssembly<T>(name, search, defaultCtx));
+ Type[] matchingTypes = manLib.TryGetAllMatchingTypes<T>().ToArray();
+
+ //try to get the first type that has the extern attribute, or fall back to the first public & concrete type
+ Type? exported = matchingTypes.FirstOrDefault(t => t.GetCustomAttribute<ExternServiceAttribute>() != null)
+ ?? matchingTypes.Where(t => !t.IsAbstract && t.IsPublic).FirstOrDefault();
+
+ _ = exported ?? throw new TypeLoadException($"The desired external asset type {typeof(T).Name} is not exported as part of the assembly {manLib.Assembly.FullName}");
+
+ //Try to get a configuration for the exported type
+ if (plugin.HasConfigForType(exported))
+ {
+ //Get the config for the type and create the service
+ return (T)CreateService(plugin, exported, plugin.GetConfigForType(exported));
+ }
+
+ //Create new instance of the desired type
+ return (T)CreateService(plugin, exported, null);
+ }
+
+
+ /// <summary>
/// <para>
/// Gets or inializes a singleton service of the desired type.
/// </para>
@@ -395,11 +407,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// <exception cref="EntryPointNotFoundException"></exception>
/// <exception cref="ConcreteTypeNotFoundException"></exception>
/// <exception cref="ConcreteTypeAmbiguousMatchException"></exception>
- public static T GetOrCreateSingleton<T>(this PluginBase plugin)
- {
- //Add service to service continer
- return GetOrCreateSingleton(plugin, CreateService<T>);
- }
+ public static T GetOrCreateSingleton<T>(this PluginBase plugin) => GetOrCreateSingleton(plugin, CreateService<T>);
/// <summary>
/// <para>
@@ -423,11 +431,8 @@ namespace VNLib.Plugins.Extensions.Loading
/// <exception cref="EntryPointNotFoundException"></exception>
/// <exception cref="ConcreteTypeNotFoundException"></exception>
/// <exception cref="ConcreteTypeAmbiguousMatchException"></exception>
- public static T GetOrCreateSingleton<T>(this PluginBase plugin, string configName)
- {
- //Add service to service continer
- return GetOrCreateSingleton(plugin, (plugin) => CreateService<T>(plugin, configName));
- }
+ public static T GetOrCreateSingleton<T>(this PluginBase plugin, string configName)
+ => GetOrCreateSingleton(plugin, (plugin) => CreateService<T>(plugin, configName));
/// <summary>
/// Configures the service asynchronously on the plugin's scheduler and returns a task
@@ -534,10 +539,7 @@ namespace VNLib.Plugins.Extensions.Loading
/// <exception cref="EntryPointNotFoundException"></exception>
/// <exception cref="ConcreteTypeNotFoundException"></exception>
/// <exception cref="ConcreteTypeAmbiguousMatchException"></exception>
- public static T CreateService<T>(this PluginBase plugin, IConfigScope? config)
- {
- return (T)CreateService(plugin, typeof(T), config);
- }
+ public static T CreateService<T>(this PluginBase plugin, IConfigScope? config) => (T)CreateService(plugin, typeof(T), config);
/// <summary>
/// <para>
@@ -591,7 +593,7 @@ namespace VNLib.Plugins.Extensions.Loading
ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) });
//Make sure the constructor exists
- _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}");
+ _ = constructor ?? throw new MissingMemberException($"No constructor found for {serviceType.Name}");
//Call constructore
service = constructor.Invoke(new object[2] { plugin, config });
@@ -602,7 +604,7 @@ namespace VNLib.Plugins.Extensions.Loading
ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase) });
//Make sure the constructor exists
- _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}");
+ _ = constructor ?? throw new MissingMemberException($"No constructor found for {serviceType.Name}");
//Call constructore
service = constructor.Invoke(new object[1] { plugin });
@@ -610,7 +612,8 @@ namespace VNLib.Plugins.Extensions.Loading
}
catch(TargetInvocationException te) when (te.InnerException != null)
{
- throw te.InnerException;
+ FindAndThrowInnerException(te);
+ throw;
}
Task? loading = null;
@@ -654,6 +657,20 @@ namespace VNLib.Plugins.Extensions.Loading
return service;
}
+ [DoesNotReturn]
+ internal static void FindAndThrowInnerException(Exception ex)
+ {
+ //Recursivley search for the innermost exception of a TIE
+ if (ex is TargetInvocationException && ex.InnerException != null)
+ {
+ FindAndThrowInnerException(ex.InnerException);
+ }
+ else
+ {
+ ExceptionDispatchInfo.Throw(ex);
+ }
+ }
+
private sealed class PluginLocalCache
{