diff options
Diffstat (limited to 'lib')
34 files changed, 1260 insertions, 576 deletions
diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs index eeeb70b..c07a02e 100644 --- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs +++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs @@ -52,7 +52,7 @@ namespace VNLib.Hashing public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "ARGON2_DLL_PATH"; private static readonly Encoding LocEncoding = Encoding.Unicode; - private static readonly Lazy<IUnmangedHeap> _heap = new (MemoryUtil.InitializeNewHeapForProcess, LazyThreadSafetyMode.PublicationOnly); + private static readonly Lazy<IUnmangedHeap> _heap = new (static () => MemoryUtil.InitializeNewHeapForProcess(true), LazyThreadSafetyMode.PublicationOnly); private static readonly Lazy<SafeArgon2Library> _nativeLibrary = new(LoadSharedLibInternal, LazyThreadSafetyMode.PublicationOnly); @@ -130,7 +130,7 @@ namespace VNLib.Hashing int passBytes = LocEncoding.GetByteCount(password); //Alloc memory for salt - using IMemoryHandle<byte> buffer = PwHeap.Alloc<byte>(saltbytes + passBytes, true); + using IMemoryHandle<byte> buffer = PwHeap.Alloc<byte>(saltbytes + passBytes); Span<byte> saltBuffer = buffer.AsSpan(0, saltbytes); Span<byte> passBuffer = buffer.AsSpan(passBytes); @@ -170,7 +170,7 @@ namespace VNLib.Hashing int passBytes = LocEncoding.GetByteCount(password); //Alloc memory for password - using IMemoryHandle<byte> pwdHandle = PwHeap.Alloc<byte>(passBytes, true); + using IMemoryHandle<byte> pwdHandle = PwHeap.Alloc<byte>(passBytes); //Encode password, create a new span to make sure its proper size _ = LocEncoding.GetBytes(password, pwdHandle.Span); @@ -317,7 +317,7 @@ namespace VNLib.Hashing int rawPassLen = LocEncoding.GetByteCount(rawPass); //Alloc buffer for decoded data - using IMemoryHandle<byte> rawBufferHandle = PwHeap.Alloc<byte>(passBase64BufSize + saltBase64BufSize + rawPassLen, true); + using IMemoryHandle<byte> rawBufferHandle = PwHeap.Alloc<byte>(passBase64BufSize + saltBase64BufSize + rawPassLen); //Split buffers Span<byte> saltBuf = rawBufferHandle.Span[..saltBase64BufSize]; @@ -375,7 +375,7 @@ namespace VNLib.Hashing ) { //Alloc data for hash output - using IMemoryHandle<byte> outputHandle = PwHeap.Alloc<byte>(hashBytes.Length, true); + using IMemoryHandle<byte> outputHandle = PwHeap.Alloc<byte>(hashBytes.Length); //Pin to get the base pointer using MemoryHandle outputPtr = outputHandle.Pin(0); diff --git a/lib/Net.Compression/vnlib_compress/compression.c b/lib/Net.Compression/vnlib_compress/compression.c index dc681c4..56d9157 100644 --- a/lib/Net.Compression/vnlib_compress/compression.c +++ b/lib/Net.Compression/vnlib_compress/compression.c @@ -46,19 +46,19 @@ */ VNLIB_EXPORT CompressorType VNLIB_CC GetSupportedCompressors(void); -VNLIB_EXPORT int64_t VNLIB_CC GetCompressorBlockSize(_In_ void* compressor); +VNLIB_EXPORT int64_t VNLIB_CC GetCompressorBlockSize(_In_ const void* compressor); -VNLIB_EXPORT CompressorType VNLIB_CC GetCompressorType(_In_ void* compressor); +VNLIB_EXPORT CompressorType VNLIB_CC GetCompressorType(_In_ const void* compressor); -VNLIB_EXPORT CompressionLevel VNLIB_CC GetCompressorLevel(_In_ void* compressor); +VNLIB_EXPORT CompressionLevel VNLIB_CC GetCompressorLevel(_In_ const void* compressor); VNLIB_EXPORT void* VNLIB_CC AllocateCompressor(CompressorType type, CompressionLevel level); VNLIB_EXPORT int VNLIB_CC FreeCompressor(_In_ void* compressor); -VNLIB_EXPORT int64_t VNLIB_CC GetCompressedSize(_In_ void* compressor, uint64_t inputLength, int32_t flush); +VNLIB_EXPORT int64_t VNLIB_CC GetCompressedSize(_In_ const void* compressor, uint64_t inputLength, int32_t flush); -VNLIB_EXPORT int VNLIB_CC CompressBlock(_In_ void* compressor, CompressionOperation* operation); +VNLIB_EXPORT int VNLIB_CC CompressBlock(_In_ const void* compressor, CompressionOperation* operation); /* Gets the supported compressors, this is defined at compile time and is a convenience method for @@ -89,7 +89,7 @@ VNLIB_EXPORT CompressorType VNLIB_CC GetSupportedCompressors(void) return supported; } -VNLIB_EXPORT CompressorType VNLIB_CC GetCompressorType(_In_ void* compressor) +VNLIB_EXPORT CompressorType VNLIB_CC GetCompressorType(_In_ const void* compressor) { if (!compressor) { @@ -99,7 +99,7 @@ VNLIB_EXPORT CompressorType VNLIB_CC GetCompressorType(_In_ void* compressor) return ((CompressorState*)compressor)->type; } -VNLIB_EXPORT CompressionLevel VNLIB_CC GetCompressorLevel(_In_ void* compressor) +VNLIB_EXPORT CompressionLevel VNLIB_CC GetCompressorLevel(_In_ const void* compressor) { if (!compressor) { @@ -109,7 +109,7 @@ VNLIB_EXPORT CompressionLevel VNLIB_CC GetCompressorLevel(_In_ void* compressor) return ((CompressorState*)compressor)->level; } -VNLIB_EXPORT int64_t VNLIB_CC GetCompressorBlockSize(_In_ void* compressor) +VNLIB_EXPORT int64_t VNLIB_CC GetCompressorBlockSize(_In_ const void* compressor) { if (!compressor) { @@ -119,8 +119,6 @@ VNLIB_EXPORT int64_t VNLIB_CC GetCompressorBlockSize(_In_ void* compressor) return (int64_t)((CompressorState*)compressor)->blockSize; } - - VNLIB_EXPORT void* VNLIB_CC AllocateCompressor(CompressorType type, CompressionLevel level) { int result; @@ -214,7 +212,7 @@ VNLIB_EXPORT void* VNLIB_CC AllocateCompressor(CompressorType type, CompressionL } } -VNLIB_EXPORT int VNLIB_CC FreeCompressor(_In_ const void* compressor) +VNLIB_EXPORT int VNLIB_CC FreeCompressor(_In_ void* compressor) { CompressorState* comp; int errorCode; diff --git a/lib/Net.Messaging.FBM/src/Client/FBMClient.cs b/lib/Net.Messaging.FBM/src/Client/FBMClient.cs index d24aca8..5184c38 100644 --- a/lib/Net.Messaging.FBM/src/Client/FBMClient.cs +++ b/lib/Net.Messaging.FBM/src/Client/FBMClient.cs @@ -73,7 +73,7 @@ namespace VNLib.Net.Messaging.FBM.Client private readonly SemaphoreSlim SendLock; private readonly ConcurrentDictionary<int, FBMRequest> ActiveRequests; - private readonly ReusableStore<FBMRequest> RequestRental; + private readonly ObjectRental<FBMRequest> RequestRental; /// <summary> /// The configuration for the current client @@ -539,6 +539,7 @@ namespace VNLib.Net.Messaging.FBM.Client /// <param name="vms">The raw response packet from the server</param> private void ProcessControlFrame(VnMemoryStream vms) { + Debug("Client control frame received. Size: {size}", vms.Length.ToString()); vms.Dispose(); } diff --git a/lib/Net.Messaging.FBM/src/Server/FBMListener.cs b/lib/Net.Messaging.FBM/src/Server/FBMListener.cs index fd8b025..46ee160 100644 --- a/lib/Net.Messaging.FBM/src/Server/FBMListener.cs +++ b/lib/Net.Messaging.FBM/src/Server/FBMListener.cs @@ -74,6 +74,7 @@ namespace VNLib.Net.Messaging.FBM.Server { Heap = heap; } +#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task /// <summary> /// Begins listening for requests on the current websocket until @@ -86,6 +87,9 @@ namespace VNLib.Net.Messaging.FBM.Server /// <returns>A <see cref="Task"/> that completes when the connection closes</returns> public async Task ListenAsync(WebSocketSession wss, RequestHandler handler, FBMListenerSessionParams args, object? userState) { + _ = wss ?? throw new ArgumentNullException(nameof(wss)); + _ = handler ?? throw new ArgumentNullException(nameof(handler)); + ListeningSession session = new(wss, handler, in args, userState); //Alloc a recieve buffer @@ -112,10 +116,10 @@ namespace VNLib.Net.Messaging.FBM.Server //break listen loop break; } - //create buffer for storing data - VnMemoryStream request = new(Heap); - //Copy initial data - request.Write(recvBuffer.Memory.Span[..result.Count]); + + //create buffer for storing data, pre alloc with initial data + VnMemoryStream request = new(Heap, recvBuffer.Memory[..result.Count]); + //Streaming read while (!result.EndOfMessage) { @@ -213,18 +217,17 @@ namespace VNLib.Net.Messaging.FBM.Server } //Get response data + await using IAsyncMessageReader messageEnumerator = await context.Response.GetResponseDataAsync(session.CancellationToken); + //Load inital segment if (await messageEnumerator.MoveNextAsync() && !session.CancellationToken.IsCancellationRequested) { ValueTask sendTask; //Syncrhonize access to send data because we may need to stream data to the client - if (!session.ResponseLock.Wait(0)) - { - await session.ResponseLock.WaitAsync(SEND_SEMAPHORE_TIMEOUT_MS); - } + await session.ResponseLock.WaitAsync(SEND_SEMAPHORE_TIMEOUT_MS); try { @@ -284,9 +287,11 @@ namespace VNLib.Net.Messaging.FBM.Server return Task.CompletedTask; } +#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task + private sealed class ListeningSession { - private readonly ReusableStore<FBMContext> CtxStore; + private readonly ObjectRental<FBMContext> CtxStore; private readonly CancellationTokenSource Cancellation; private readonly CancellationTokenRegistration Registration; private readonly FBMListenerSessionParams Params; diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs index 763eb26..c4d602b 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs @@ -50,6 +50,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction private Action<ICollection<IServiceHost>>? _hostBuilder; private Func<ServiceGroup, IHttpServer>? _getServers; private Func<IPluginStack>? _getPlugins; + private IManualPlugin[]? manualPlugins; /// <summary> /// Uses the supplied callback to get a collection of virtual hosts @@ -93,7 +94,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction /// <returns>The current instance for chaining</returns> public HttpServiceStackBuilder WithBuiltInHttp(Func<ServiceGroup, ITransportProvider> transport, HttpConfig config) { - return WithHttp(sg => new HttpServer(config, transport(sg), sg.Hosts.Select(static p => p.Processor))); + return WithBuiltInHttp(transport, sg => config); } /// <summary> @@ -108,6 +109,18 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction } /// <summary> + /// Adds a collection of manual plugin instances to the stack. Every call + /// to this method will replace the previous collection. + /// </summary> + /// <param name="plugins">The array of plugins (or params) to add</param> + /// <returns>The current instance for chaining</returns> + public HttpServiceStackBuilder WithManualPlugins(params IManualPlugin[] plugins) + { + manualPlugins = plugins; + return this; + } + + /// <summary> /// Builds the new <see cref="HttpServiceStack"/> from the configured callbacks /// </summary> /// <returns>The newly constructed <see cref="HttpServiceStack"/> that may be used to manage your http services</returns> @@ -137,6 +150,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction servers.AddLast(server); } + //Always init manual array + manualPlugins ??= Array.Empty<IManualPlugin>(); + //Only load plugins if the callback is configured IPluginStack? plugins = _getPlugins?.Invoke(); @@ -144,7 +160,9 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction plugins ??= new EmptyPluginStack(); #pragma warning restore CA2000 // Dispose objects before losing scope - return new(servers, sd, plugins); + IPluginInitializer init = new PluginStackInitializer(plugins, manualPlugins); + + return new(servers, sd, init); } /* diff --git a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs index 51c3091..7cd5ac4 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs @@ -29,7 +29,6 @@ using System.Collections.Generic; using VNLib.Utils; using VNLib.Net.Http; -using VNLib.Plugins.Runtime; namespace VNLib.Plugins.Essentials.ServiceStack { @@ -62,7 +61,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// generate servers to listen for services exposed by the /// specified host context /// </summary> - internal HttpServiceStack(LinkedList<IHttpServer> servers, ServiceDomain serviceDomain, IPluginStack plugins) + internal HttpServiceStack(LinkedList<IHttpServer> servers, ServiceDomain serviceDomain, IPluginInitializer plugins) { _servers = servers; _serviceDomain = serviceDomain; diff --git a/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs index 852bb95..21c8e91 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs @@ -22,30 +22,17 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ +using System; using System.ComponentModel.Design; -using VNLib.Plugins.Runtime; - namespace VNLib.Plugins.Essentials.ServiceStack { - - /// <summary> /// Represents a plugin managed by a <see cref="IHttpPluginManager"/> 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> @@ -53,5 +40,30 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// must listen for plugin load/unload events to respect lifecycles properly. /// </remarks> 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(); + + /// <summary> + /// Internal notification that the plugin is unloaded + /// </summary> + internal void OnPluginUnloaded(); + + /// <summary> + /// Sends the specified command to the desired plugin by it's name + /// </summary> + /// <param name="pluginName">The name of the plugin to find</param> + /// <param name="command">The command text to send to the plugin</param> + /// <param name="comp">The string name comparison type</param> + /// <returns>True if the command was sent successfully</returns> + internal bool SendCommandToPlugin(string pluginName, string command, StringComparison comp); } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs new file mode 100644 index 0000000..e58067f --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs @@ -0,0 +1,77 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: IManualPlugin.cs +* +* IManualPlugin.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.ComponentModel.Design; + +namespace VNLib.Plugins.Essentials.ServiceStack +{ + /// <summary> + /// Represents a plugin that may be added to a service stack in user-code + /// instead of the conventional runtime plugin loading system + /// </summary> + public interface IManualPlugin : IDisposable + { + /// <summary> + /// The name of the plugin + /// </summary> + string Name { get; } + + /// <summary> + /// Collects all exported services for use within the service stack + /// </summary> + /// <param name="container">The container to add services to</param> + 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(); + + /// <summary> + /// Loads the plugin, called after initialization but before getting + /// endpoints or services to allow for the plugin to configure itself + /// and perform initial setup + /// </summary> + void Load(); + + /// <summary> + /// Called when an unload was requested, either manually by the plugin controller + /// or when the service stack is unloading + /// </summary> + void Unload(); + + /// <summary> + /// Passes a console command to the plugin + /// </summary> + /// <param name="command">The raw command text to pass to the plugin from the console</param> + void OnConsoleCommand(string command); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs new file mode 100644 index 0000000..ac91f45 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: IPluginInitializer.cs +* +* IPluginInitializer.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.Utils.Logging; +using VNLib.Plugins.Runtime; + +namespace VNLib.Plugins.Essentials.ServiceStack +{ + internal interface IPluginInitializer + { + void PrepareStack(IPluginEventListener listener); + + IManagedPlugin[] InitializePluginStack(ILogProvider eventLogger); + + void UnloadPlugins(); + + void ReloadPlugins(); + + void Dispose(); + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs b/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs deleted file mode 100644 index 0c0dfa5..0000000 --- a/lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs +++ /dev/null @@ -1,99 +0,0 @@ -/* -* 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.Linq; -using System.Reflection; -using System.ComponentModel.Design; - -using VNLib.Plugins.Runtime; -using VNLib.Plugins.Attributes; - - -namespace VNLib.Plugins.Essentials.ServiceStack -{ - - internal sealed class ManagedPlugin : IManagedPlugin - { - internal RuntimePluginLoader Plugin { get; } - - ///<inheritdoc/> - public string PluginPath => Plugin.Config.AssemblyFile; - - private ServiceContainer? _services; - - public ManagedPlugin(RuntimePluginLoader loader) => Plugin = loader; - - ///<inheritdoc/> - public IServiceContainer Services - { - get - { - _ = _services ?? throw new InvalidOperationException("The service container is not currently loaded"); - return _services!; - } - } - - ///<inheritdoc/> - public PluginController Controller => Plugin.Controller; - - /* - * Automatically called after the plugin has successfully loaded - * by event handlers below - */ - - internal void OnPluginLoaded() - { - //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 OnPluginUnloaded() - { - //Cleanup services no longer in use. Plugin is still valid until this method returns - _services?.Dispose(); - //Remove ref to services - _services = null; - } - } -} diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs index 476f3f8..2f57367 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs @@ -24,10 +24,7 @@ using System; -using System.IO; using System.Linq; -using System.Diagnostics; -using System.Threading.Tasks; using System.Collections.Generic; using VNLib.Utils; @@ -43,23 +40,22 @@ namespace VNLib.Plugins.Essentials.ServiceStack /// instances, exposes controls, and relays stateful plugin events. /// </summary> internal sealed class PluginManager : VnDisposeable, IHttpPluginManager, IPluginEventListener - { - private readonly Dictionary<PluginController, ManagedPlugin> _managedPlugins; + { private readonly ServiceDomain _dependents; - private readonly IPluginStack _stack; - - private IEnumerable<LivePlugin> _livePlugins => _managedPlugins.SelectMany(static p => p.Key.Plugins); + private readonly IPluginInitializer _stack; /// <summary> /// The collection of internal controllers /// </summary> - public IEnumerable<IManagedPlugin> Plugins => _managedPlugins.Select(static p => p.Value); + public IEnumerable<IManagedPlugin> Plugins => _loadedPlugins; + + private IManagedPlugin[] _loadedPlugins; - public PluginManager(ServiceDomain dependents, IPluginStack stack) + public PluginManager(ServiceDomain dependents, IPluginInitializer stack) { _dependents = dependents; _stack = stack; - _managedPlugins = new(); + _loadedPlugins = Array.Empty<IManagedPlugin>(); } /// <summary> @@ -70,104 +66,29 @@ namespace VNLib.Plugins.Essentials.ServiceStack { _ = _stack ?? throw new InvalidOperationException("Plugin stack has not been set."); - /* - * Since we own the plugin stack, it is safe to build it here. - * This method is not public and should not be called more than - * once. Otherwise it can cause issues with the plugin stack. - */ - _stack.BuildStack(); - - //Register for plugin events - _stack.RegsiterListener(this, this); - - //Create plugin wrappers from loaded plugins - ManagedPlugin[] wrapper = _stack.Plugins.Select(p => new ManagedPlugin(p)).ToArray(); + _stack.PrepareStack(this); - //Add all wrappers to the managed plugins table - Array.ForEach(wrapper, w => _managedPlugins.Add(w.Plugin.Controller, w)); - - //Init remaining controllers single-threaded because it may mutate the table - _managedPlugins.Select(p => p.Value).TryForeach(w => InitializePlugin(w.Plugin, debugLog)); - - //Load stage, load all multithreaded - Parallel.ForEach(_managedPlugins.Values, wp => LoadPlugin(wp.Plugin, debugLog)); + //Initialize the plugin stack and store the loaded plugins + _loadedPlugins = _stack.InitializePluginStack(debugLog); debugLog.Information("Plugin loading completed"); } - /* - * Plugins are manually loaded by this manager instead of the stack shortcut extensions - * because I want to catch individual exceptions. - * - * I do not prefer this method as I would prefer loading is handled by the stack - * and the host not by this library. - * - * This will change in the future. - */ - - private void InitializePlugin(RuntimePluginLoader plugin, ILogProvider debugLog) - { - string fileName = Path.GetFileName(plugin.Config.AssemblyFile); - - try - { - //Initialzie plugin wrapper - plugin.InitializeController(); - - /* - * If the plugin assembly does not expose any plugin types or there is an issue loading the assembly, - * its types my not unify, then we should give the user feedback insead of a silent fail. - */ - if (!plugin.Controller.Plugins.Any()) - { - debugLog.Warn("No plugin instances were exposed via {asm} assembly. This may be due to an assebmly mismatch", fileName); - } - } - catch (Exception ex) - { - debugLog.Error("Exception raised during initialzation of {asm}. It has been removed from the collection\n{ex}", fileName, ex); - - //Remove the plugin from the table - _managedPlugins.Remove(plugin.Controller); - } - } - - private static void LoadPlugin(RuntimePluginLoader plugin, ILogProvider debugLog) - { - string fileName = Path.GetFileName(plugin.Config.AssemblyFile); - - Stopwatch sw = new(); - try - { - sw.Start(); - - //Load wrapper - plugin.LoadPlugins(); - - sw.Stop(); - - debugLog.Verbose("Loaded {pl} in {tm} ms", fileName, sw.ElapsedMilliseconds); - } - catch (Exception ex) - { - debugLog.Error("Exception raised during loading {asf}. Failed to load plugin \n{ex}", fileName, 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(); + foreach(IManagedPlugin plugin in _loadedPlugins) + { + if(plugin.SendCommandToPlugin(pluginName, message, nameComparison)) + { + return true; + } + } - //Send the command - return pl?.SendConsoleMessage(message) ?? false; + return false; } /// <inheritdoc/> @@ -176,7 +97,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack Check(); //Reload all plugins, causing an event cascade - _stack.ReloadAll(); + _stack.ReloadPlugins(); } /// <inheritdoc/> @@ -185,7 +106,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack Check(); //Unload all plugin controllers - _stack.UnloadAll(); + _stack.UnloadPlugins(); /* * All plugin instances must be destroyed because the @@ -198,26 +119,15 @@ namespace VNLib.Plugins.Essentials.ServiceStack protected override void Free() { //Clear plugin table - _managedPlugins.Clear(); + _loadedPlugins = Array.Empty<IManagedPlugin>(); //Dispose the plugin stack _stack.Dispose(); } - /* - * When using a service stack an loading manually, plugins that have errors - * will not be captured by this instance. However when using the shortcut - * extensions, the events will be invoked regaldess if we loaded the plugin - * here. - */ - void IPluginEventListener.OnPluginLoaded(PluginController controller, object? state) { - //Make sure the plugin is managed by this manager - if(!_managedPlugins.TryGetValue(controller, out ManagedPlugin? mp)) - { - return; - } + IManagedPlugin mp = (state as IManagedPlugin)!; //Run onload method before invoking other handlers mp.OnPluginLoaded(); @@ -231,11 +141,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack void IPluginEventListener.OnPluginUnloaded(PluginController controller, object? state) { - //Make sure the plugin is managed by this manager - if (!_managedPlugins.TryGetValue(controller, out ManagedPlugin? mp)) - { - return; - } + IManagedPlugin plugin = (state as IManagedPlugin)!; try { @@ -243,12 +149,12 @@ namespace VNLib.Plugins.Essentials.ServiceStack ServiceGroup[] deps = _dependents.ServiceGroups.Select(static d => d).ToArray(); //Run unloaded method - deps.TryForeach(d => d.OnPluginUnloaded(mp)); + deps.TryForeach(d => d.OnPluginUnloaded(plugin)); } finally { //always unload the plugin wrapper - mp.OnPluginUnloaded(); + plugin.OnPluginUnloaded(); } } } diff --git a/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs b/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs new file mode 100644 index 0000000..6ccd862 --- /dev/null +++ b/lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs @@ -0,0 +1,329 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.ServiceStack +* File: PluginStackInitializer.cs +* +* PluginStackInitializer.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.Reflection; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.ComponentModel.Design; + +using VNLib.Utils.Logging; +using VNLib.Plugins.Runtime; +using VNLib.Utils.Extensions; +using VNLib.Plugins.Attributes; + +namespace VNLib.Plugins.Essentials.ServiceStack +{ + + internal sealed record class PluginStackInitializer(IPluginStack Stack, IManualPlugin[] ManualPlugins) : IPluginInitializer + { + private readonly LinkedList<IManagedPlugin> _managedPlugins = new(); + private readonly LinkedList<ManualPluginWrapper> _manualPlugins = new(); + + ///<inheritdoc/> + public void PrepareStack(IPluginEventListener listener) + { + /* + * Since we own the plugin stack, it is safe to build it here. + * This method is not public and should not be called more than + * once. Otherwise it can cause issues with the plugin stack. + */ + Stack.BuildStack(); + + //Create plugin wrappers from loaded plugins + ManagedPlugin[] wrapper = Stack.Plugins.Select(static p => new ManagedPlugin(p)).ToArray(); + + //Add wrappers to list of managed plugins + 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)); + + //Add manual plugins to list of managed plugins + Array.ForEach(ManualPlugins, p => _manualPlugins.AddLast(new ManualPluginWrapper(p))); + } + + ///<inheritdoc/> + public IManagedPlugin[] InitializePluginStack(ILogProvider debugLog) + { + //single thread initialziation + LinkedList<IManagedPlugin> _loadedPlugins = new(); + + //Combine all managed plugins and initialize them individually + IEnumerable<IManagedPlugin> plugins = _managedPlugins.Union(_manualPlugins); + + foreach(IManagedPlugin p in plugins) + { + //Try init plugin and add it to the list of loaded plugins + if (InitializePluginCore(p, debugLog)) + { + _loadedPlugins.AddLast(p); + } + } + + //Load stage, load only initialized plugins + Parallel.ForEach(_loadedPlugins, wp => LoadPlugin(wp, debugLog)); + + return _loadedPlugins.ToArray(); + } + + ///<inheritdoc/> + public void UnloadPlugins() + { + Stack.UnloadAll(); + _manualPlugins.TryForeach(static mp => mp.Unload()); + } + + ///<inheritdoc/> + public void ReloadPlugins() + { + Stack.ReloadAll(); + + //Reload manual plugins + _manualPlugins.TryForeach(static mp => mp.Unload()); + _manualPlugins.TryForeach(static mp => mp.Load()); + } + + ///<inheritdoc/> + public void Dispose() + { + Stack.Dispose(); + _manualPlugins.TryForeach(static mp => mp.Dispose()); + _manualPlugins.Clear(); + } + + private static bool InitializePluginCore(IManagedPlugin plugin, ILogProvider debugLog) + { + try + { + if (plugin is ManagedPlugin mp) + { + //Initialzie plugin wrapper + mp.Plugin.InitializeController(); + + /* + * If the plugin assembly does not expose any plugin types or there is an issue loading the assembly, + * its types my not unify, then we should give the user feedback insead of a silent fail. + */ + if (!mp.Plugin.Controller.Plugins.Any()) + { + debugLog.Warn("No plugin instances were exposed via {asm} assembly. This may be due to an assebmly mismatch", plugin.ToString()); + } + } + else if(plugin is ManualPluginWrapper mpw) + { + //Initialzie plugin wrapper + mpw.Plugin.Initialize(); + } + else + { + Debug.Fail("Missed managed plugin wrapper type"); + } + + return true; + } + catch (Exception ex) + { + debugLog.Error("Exception raised during initialzation of {asm}. It has been removed from the collection\n{ex}", plugin.ToString(), ex); + } + + return false; + } + + private static void LoadPlugin(IManagedPlugin plugin, ILogProvider debugLog) + { + Stopwatch sw = new(); + try + { + sw.Start(); + + //Recover the base class used to load instances + if (plugin is ManagedPlugin mp) + { + mp.Plugin.LoadPlugins(); + } + else if (plugin is ManualPluginWrapper mpw) + { + mpw.Load(); + } + else + { + Debug.Fail("Missed managed plugin wrapper type"); + } + + sw.Stop(); + + debugLog.Verbose("Loaded {pl} in {tm} ms", plugin.ToString(), sw.ElapsedMilliseconds); + } + catch (Exception ex) + { + debugLog.Error("Exception raised during loading {asf}. Failed to load plugin \n{ex}", plugin.ToString(), ex); + } + finally + { + sw.Stop(); + } + } + + + private sealed record class ManagedPlugin(RuntimePluginLoader Plugin) : IManagedPlugin + { + private ServiceContainer? _services; + + ///<inheritdoc/> + public IServiceContainer Services + { + get + { + _ = _services ?? throw new InvalidOperationException("The service container is not currently loaded"); + return _services!; + } + } + + ///<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 + */ + + ///<inheritdoc/> + void IManagedPlugin.OnPluginLoaded() + { + //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); + } + } + + ///<inheritdoc/> + void IManagedPlugin.OnPluginUnloaded() + { + //Cleanup services no longer in use. Plugin is still valid until this method returns + _services?.Dispose(); + + //Remove ref to services + _services = null; + } + + ///<inheritdoc/> + bool IManagedPlugin.SendCommandToPlugin(string pluginName, string command, StringComparison comp) + { + //Get plugin + LivePlugin? plugin = Plugin.Controller.Plugins.FirstOrDefault(p => p.PluginName.Equals(pluginName, comp)); + + //If plugin is null, return false + if (plugin == null) + { + return false; + } + + return plugin.SendConsoleMessage(command); + } + + public override string ToString() => Path.GetFileName(Plugin.Config.AssemblyFile); + } + + private sealed record class ManualPluginWrapper(IManualPlugin Plugin) : IManagedPlugin, IDisposable + { + private ServiceContainer _container = new(); + + ///<inheritdoc/> + public IServiceContainer Services => _container; + + ///<inheritdoc/> + IEndpoint[] IManagedPlugin.GetEndpoints() => Plugin.GetEndpoints(); + + public void Load() + { + Plugin.Load(); + Plugin.GetAllExportedServices(Services); + } + + public void Unload() + { + Plugin.Unload(); + + //Unload and re-init container + _container.Dispose(); + _container = new(); + } + + public void Dispose() + { + //Dispose container + _container.Dispose(); + + //Dispose plugin + Plugin.Dispose(); + } + + ///<inheritdoc/> + bool IManagedPlugin.SendCommandToPlugin(string pluginName, string command, StringComparison comp) + { + + if (Plugin.Name.Equals(pluginName, comp)) + { + Plugin.OnConsoleCommand(command); + return true; + } + + return false; + } + + + /* + * SHOULD NEVER BE CALLED + */ + void IManagedPlugin.OnPluginLoaded() => throw new NotImplementedException(); + void IManagedPlugin.OnPluginUnloaded() => throw new NotImplementedException(); + + ///<inheritdoc/> + public override string ToString() => Plugin.Name; + } + + } +} diff --git a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs index c8be44c..e504013 100644 --- a/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs +++ b/lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs @@ -23,7 +23,6 @@ */ using System.Net; -using System.Linq; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -80,28 +79,28 @@ namespace VNLib.Plugins.Essentials.ServiceStack _endpointsForPlugins.Clear(); } - internal void OnPluginLoaded(IManagedPlugin controller) + internal void OnPluginLoaded(IManagedPlugin plugin) { //Get all new endpoints for plugin - IEndpoint[] newEndpoints = controller.Controller.GetOnlyWebPlugins().SelectMany(static pl => pl.Plugin!.GetEndpoints()).ToArray(); + IEndpoint[] newEndpoints = plugin.GetEndpoints(); //Add endpoints to dict - _endpointsForPlugins.AddOrUpdate(controller, newEndpoints); + _endpointsForPlugins.AddOrUpdate(plugin, newEndpoints); //Add endpoints to hosts - _vHosts.TryForeach(v => v.OnRuntimeServiceAttach(controller, newEndpoints)); + _vHosts.TryForeach(v => v.OnRuntimeServiceAttach(plugin, newEndpoints)); } - internal void OnPluginUnloaded(IManagedPlugin controller) + internal void OnPluginUnloaded(IManagedPlugin plugin) { //Get the old endpoints from the controller referrence and remove them - if (_endpointsForPlugins.TryGetValue(controller, out IEndpoint[]? oldEps)) + if (_endpointsForPlugins.TryGetValue(plugin, out IEndpoint[]? oldEps)) { //Remove the old endpoints - _vHosts.TryForeach(v => v.OnRuntimeServiceDetach(controller, oldEps)); + _vHosts.TryForeach(v => v.OnRuntimeServiceDetach(plugin, oldEps)); //remove controller ref - _ = _endpointsForPlugins.Remove(controller); + _ = _endpointsForPlugins.Remove(plugin); } } } diff --git a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs index 7f3ae40..54deb8c 100644 --- a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs +++ b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs @@ -595,25 +595,6 @@ namespace VNLib.Plugins.Essentials.Accounts user.SetValueType(FAILED_LOGIN_ENTRY, value.ToUInt64()); } - /// <summary> - /// Increments the failed login attempt count - /// </summary> - /// <param name="user"></param> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void FailedLoginIncrement(this IUser user) => FailedLoginIncrement(user, DateTimeOffset.UtcNow); - - /// <summary> - /// Increments the failed login attempt count - /// </summary> - /// <param name="user"></param> - /// <param name="time">Explicitly set the time of the counter</param> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void FailedLoginIncrement(this IUser user, DateTimeOffset time) - { - TimestampedCounter current = user.FailedLoginCount(); - user.FailedLoginCount(current.Count + 1, time); - } - #endregion } }
\ No newline at end of file diff --git a/lib/Plugins.Essentials/src/Accounts/FailedLoginLockout.cs b/lib/Plugins.Essentials/src/Accounts/FailedLoginLockout.cs new file mode 100644 index 0000000..a67eee2 --- /dev/null +++ b/lib/Plugins.Essentials/src/Accounts/FailedLoginLockout.cs @@ -0,0 +1,137 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials +* File: FailedLoginLockout.cs +* +* FailedLoginLockout.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; + +using VNLib.Plugins.Essentials.Users; + +#nullable enable + +namespace VNLib.Plugins.Essentials.Accounts +{ + /// <summary> + /// Allows tracking of failed login attempts and lockout of accounts + /// </summary> + public class FailedLoginLockout + { + private readonly uint _maxCounts; + private readonly TimeSpan _duration; + + /// <summary> + /// Initializes a new instance of the <see cref="FailedLoginLockout"/> class. + /// </summary> + /// <param name="maxCounts">The max number of failed login attempts before a lockout occurs</param> + /// <param name="maxTimeout">The max duration for a lockout to last</param> + public FailedLoginLockout(uint maxCounts, TimeSpan maxTimeout) + { + _maxCounts = maxCounts; + _duration = maxTimeout; + } + + /// <summary> + /// Increments the lockout counter for the supplied user. If the lockout count + /// has been exceeded, it is not incremented. If the lockout count has expired, + /// it is reset and 0 is returned. + /// </summary> + /// <param name="user">The user to increment the failed login count</param> + /// <param name="now">The current time</param> + /// <returns>The new lockout count after incrementing or 0 if the count was cleared</returns> + public bool IncrementOrClear(IUser user, DateTimeOffset now) + { + //Recover last counter value + TimestampedCounter current = user.FailedLoginCount(); + + //See if the flc timeout period has expired + if (current.LastModified.Add(_duration) < now) + { + //clear flc flag + user.ClearFailedLoginCount(); + return false; + } + + if (current.Count <= _maxCounts) + { + //Increment counter + user.FailedLoginCount(current.Count + 1, now); + return false; + } + + return true; + } + + /// <summary> + /// Checks if the current user's lockout count has been exceeded, or + /// clears a previous lockout if the timeout period has expired. + /// </summary> + /// <param name="user">The user to increment the failed login count</param> + /// <param name="now">The current time</param> + /// <returns>A value that indicates if the count has been exceeded</returns> + public bool CheckOrClear(IUser user, DateTimeOffset now) + { + //Recover last counter value + TimestampedCounter flc = user.FailedLoginCount(); + + //See if the flc timeout period has expired + if (flc.LastModified.Add(_duration) < now) + { + //clear flc flag + user.ClearFailedLoginCount(); + return false; + } + + return flc.Count >= _maxCounts; + } + + /// <summary> + /// Checks if the current user's lockout count has been exceeded + /// </summary> + /// <param name="user">The user to check the counter for</param> + /// <returns>True if the lockout has been exceeded</returns> + public bool IsCountExceeded(IUser user) + { + //Recover last counter value + TimestampedCounter flc = user.FailedLoginCount(); + //Count has been exceeded, and has not timed out yet + return flc.Count >= _maxCounts; + } + + /// <summary> + /// Increments the lockout counter for the supplied user. + /// </summary> + /// <param name="user">The user to increment the count on</param> + /// <param name="now"></param> + public void Increment(IUser user, DateTimeOffset now) + { + //Recover last counter value + TimestampedCounter current = user.FailedLoginCount(); + + //Only increment if the count is less than max counts + if (current.Count <= _maxCounts) + { + //Increment counter + user.FailedLoginCount(current.Count + 1, now); + } + } + } +}
\ No newline at end of file diff --git a/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs b/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs index dfb94f7..92643d9 100644 --- a/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs +++ b/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs @@ -199,51 +199,33 @@ namespace VNLib.Plugins.Essentials.Endpoints /// </summary> /// <param name="entity">The entity to be processed</param> /// <returns>The result of the operation to return to the file processor</returns> - protected virtual ValueTask<VfReturnType> PostAsync(HttpEntity entity) - { - return ValueTask.FromResult(Post(entity)); - } + protected virtual ValueTask<VfReturnType> PostAsync(HttpEntity entity) => ValueTask.FromResult(Post(entity)); /// <summary> /// This method gets invoked when an incoming GET request to the endpoint has been requested. /// </summary> /// <param name="entity">The entity to be processed</param> /// <returns>The result of the operation to return to the file processor</returns> - protected virtual ValueTask<VfReturnType> GetAsync(HttpEntity entity) - { - return ValueTask.FromResult(Get(entity)); - } + protected virtual ValueTask<VfReturnType> GetAsync(HttpEntity entity) => ValueTask.FromResult(Get(entity)); /// <summary> /// This method gets invoked when an incoming DELETE request to the endpoint has been requested. /// </summary> /// <param name="entity">The entity to be processed</param> /// <returns>The result of the operation to return to the file processor</returns> - protected virtual ValueTask<VfReturnType> DeleteAsync(HttpEntity entity) - { - return ValueTask.FromResult(Delete(entity)); - } + protected virtual ValueTask<VfReturnType> DeleteAsync(HttpEntity entity) => ValueTask.FromResult(Delete(entity)); /// <summary> /// This method gets invoked when an incoming PUT request to the endpoint has been requested. /// </summary> /// <param name="entity">The entity to be processed</param> /// <returns>The result of the operation to return to the file processor</returns> - protected virtual ValueTask<VfReturnType> PutAsync(HttpEntity entity) - { - return ValueTask.FromResult(Put(entity)); - } + protected virtual ValueTask<VfReturnType> PutAsync(HttpEntity entity) => ValueTask.FromResult(Put(entity)); /// <summary> /// This method gets invoked when an incoming PATCH request to the endpoint has been requested. /// </summary> /// <param name="entity">The entity to be processed</param> /// <returns>The result of the operation to return to the file processor</returns> - protected virtual ValueTask<VfReturnType> PatchAsync(HttpEntity entity) - { - return ValueTask.FromResult(Patch(entity)); - } + protected virtual ValueTask<VfReturnType> PatchAsync(HttpEntity entity) => ValueTask.FromResult(Patch(entity)); - protected virtual ValueTask<VfReturnType> OptionsAsync(HttpEntity entity) - { - return ValueTask.FromResult(Options(entity)); - } + protected virtual ValueTask<VfReturnType> OptionsAsync(HttpEntity entity) => ValueTask.FromResult(Options(entity)); /// <summary> /// Invoked when a request is received for a method other than GET, POST, DELETE, or PUT; @@ -251,20 +233,14 @@ namespace VNLib.Plugins.Essentials.Endpoints /// <param name="entity">The entity that </param> /// <param name="method">The request method</param> /// <returns>The results of the processing</returns> - protected virtual ValueTask<VfReturnType> AlternateMethodAsync(HttpEntity entity, HttpMethod method) - { - return ValueTask.FromResult(AlternateMethod(entity, method)); - } + protected virtual ValueTask<VfReturnType> AlternateMethodAsync(HttpEntity entity, HttpMethod method) => ValueTask.FromResult(AlternateMethod(entity, method)); /// <summary> /// Invoked when the current endpoint received a websocket request /// </summary> /// <param name="entity">The entity that requested the websocket</param> /// <returns>The results of the operation</returns> - protected virtual ValueTask<VfReturnType> WebsocketRequestedAsync(HttpEntity entity) - { - return ValueTask.FromResult(WebsocketRequested(entity)); - } + protected virtual ValueTask<VfReturnType> WebsocketRequestedAsync(HttpEntity entity) => ValueTask.FromResult(WebsocketRequested(entity)); /// <summary> /// This method gets invoked when an incoming POST request to the endpoint has been requested. @@ -329,10 +305,7 @@ namespace VNLib.Plugins.Essentials.Endpoints return VfReturnType.VirtualSkip; } - protected virtual VfReturnType Options(HttpEntity entity) - { - return VfReturnType.Forbidden; - } + protected virtual VfReturnType Options(HttpEntity entity) => VfReturnType.Forbidden; /// <summary> /// Invoked when the current endpoint received a websocket request @@ -344,5 +317,101 @@ namespace VNLib.Plugins.Essentials.Endpoints entity.CloseResponse(HttpStatusCode.Forbidden); return VfReturnType.VirtualSkip; } + + /// <summary> + /// Shortcut helper methods to a virtual skip response with a 200 OK status code + /// </summary> + /// <param name="entity">The entity to close the connection for</param> + /// <returns>The <see cref="VfReturnType.VirtualSkip"/> operation result</returns> + public static VfReturnType VirtualOk(HttpEntity entity) => VirtualClose(entity, HttpStatusCode.OK); + + /// <summary> + /// Shortcut helper methods to a virtual skip response with a 200 OK status code + /// that returns a <see cref="WebMessage"/> json response + /// </summary> + /// <param name="entity">The entity to close the connection for</param> + /// <param name="webm">The <see cref="WebMessage"/> json response</param> + /// <returns>The <see cref="VfReturnType.VirtualSkip"/> operation result</returns> + public static VfReturnType VirtualOk<T>(HttpEntity entity, T webm) where T: WebMessage + { + entity.CloseResponse(webm); + return VfReturnType.VirtualSkip; + } + + /// <summary> + /// Shortcut helper methods to a virtual skip response with a 200 OK status code + /// that returns a <see cref="WebMessage"/> json response + /// </summary> + /// <param name="entity">The entity to close the connection for</param> + /// <param name="jsonValue">A object that will be serialized and returned to the client as JSON</param> + /// <returns>The <see cref="VfReturnType.VirtualSkip"/> operation result</returns> + public static VfReturnType VirtualOkJson<T>(HttpEntity entity, T jsonValue) => VirtualCloseJson(entity, jsonValue, HttpStatusCode.OK); + + /// <summary> + /// Shortcut helper methods to a virtual skip response with a 200 OK status code + /// that returns a <see cref="WebMessage"/> json response + /// </summary> + /// <param name="entity">The entity to close the connection for</param> + /// <param name="webm">The <see cref="WebMessage"/> json response</param> + /// <param name="code">The status code to return to the client</param> + /// <returns>The <see cref="VfReturnType.VirtualSkip"/> operation result</returns> + public static VfReturnType VirtualClose<T>(HttpEntity entity, T webm, HttpStatusCode code) where T: WebMessage => VirtualCloseJson(entity, webm, code); + + /// <summary> + /// Shortcut helper methods to a virtual skip response with a given status code + /// </summary> + /// <param name="entity">The entity to close the connection for</param> + /// <param name="code">The status code to return to the client</param> + /// <returns>The <see cref="VfReturnType.VirtualSkip"/> operation result</returns> + public static VfReturnType VirtualClose(HttpEntity entity, HttpStatusCode code) + { + entity.CloseResponse(code); + return VfReturnType.VirtualSkip; + } + + /// <summary> + /// Shortcut helper methods to a virtual skip response with a given status code, + /// and memory content to return to the client + /// </summary> + /// <param name="entity">The entity to close the connection for</param> + /// <param name="code">The status code to return to the client</param> + /// <param name="ct">The response content type</param> + /// <param name="response">The memory response to return to the user</param> + /// <returns>The <see cref="VfReturnType.VirtualSkip"/> operation result</returns> + public static VfReturnType VirtualClose(HttpEntity entity, HttpStatusCode code, ContentType ct, IMemoryResponseReader response) + { + entity.CloseResponse(code, ct, response); + return VfReturnType.VirtualSkip; + } + + /// <summary> + /// Shortcut helper methods to a virtual skip response with a given status code, + /// and memory content to return to the client + /// </summary> + /// <param name="entity">The entity to close the connection for</param> + /// <param name="code">The status code to return to the client</param> + /// <param name="ct">The response content type</param> + /// <param name="response">The stream response to return to the user</param> + /// <returns>The <see cref="VfReturnType.VirtualSkip"/> operation result</returns> + public static VfReturnType VirtualClose(HttpEntity entity, HttpStatusCode code, ContentType ct, Stream response) + { + entity.CloseResponse(code, ct, response); + return VfReturnType.VirtualSkip; + } + + /// <summary> + /// Shortcut helper methods to a virtual skip response with a given status code, and + /// a json payload to return to the client + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="entity">The entity to close the connection for</param> + /// <param name="code">The status code to return to the client</param> + /// <param name="jsonValue">A object that will be serialized and returned to the client as JSON</param> + /// <returns>The <see cref="VfReturnType.VirtualSkip"/> operation result</returns> + public static VfReturnType VirtualCloseJson<T>(HttpEntity entity, T jsonValue, HttpStatusCode code) + { + entity.CloseResponseJson(code, jsonValue); + return VfReturnType.VirtualSkip; + } } }
\ No newline at end of file diff --git a/lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs b/lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs index f0cc46a..393c838 100644 --- a/lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs +++ b/lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs @@ -112,10 +112,23 @@ namespace VNLib.Plugins.Essentials.Extensions /// Determines if the connection accepts any content type /// </summary> /// <returns>true if the connection accepts any content typ, false otherwise</returns> - private static bool AcceptsAny(this IConnectionInfo server) + private static bool AcceptsAny(IConnectionInfo server) { - //Accept any if no accept header was present, or accept all value */* - return server.Accept.Count == 0 || server.Accept.Where(static t => t.StartsWith("*/*", StringComparison.OrdinalIgnoreCase)).Any(); + if(server.Accept.Count == 0) + { + return true; + } + + //Search list for accept any + foreach(string accept in server.Accept) + { + if(accept.StartsWith("*/*", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; } /// <summary> @@ -222,10 +235,7 @@ namespace VNLib.Plugins.Essentials.Extensions /// code relies on the port number of the <see cref="ConnectionInfo.RequestUri"/> /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EnpointPortsMatch(this IConnectionInfo server) - { - return server.RequestUri.Port == server.LocalEndpoint.Port; - } + public static bool EnpointPortsMatch(this IConnectionInfo server) => server.RequestUri.Port == server.LocalEndpoint.Port; /// <summary> /// Determines if the host of the current request URI matches the referer header host /// </summary> @@ -384,7 +394,6 @@ namespace VNLib.Plugins.Essentials.Extensions /// <param name="server"></param> /// <param name="isTrusted"></param> /// <returns>The real ip of the connection</returns> - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static IPAddress GetTrustedIp(this IConnectionInfo server, bool isTrusted) { //If the connection is not trusted, then ignore header parsing @@ -400,7 +409,7 @@ namespace VNLib.Plugins.Essentials.Extensions return server.RemoteEndpoint.Address; } } - + /// <summary> /// Gets a value that determines if the connection is using tls, locally /// or behind a trusted downstream server that is using tls. @@ -408,13 +417,8 @@ namespace VNLib.Plugins.Essentials.Extensions /// <param name="server"></param> /// <returns>True if the connection is secure, false otherwise</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsSecure(this IConnectionInfo server) - { - //Get value of the trusted downstream server - return IsSecure(server, server.IsBehindDownStreamServer()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsSecure(this IConnectionInfo server) => IsSecure(server, server.IsBehindDownStreamServer()); + internal static bool IsSecure(this IConnectionInfo server, bool isTrusted) { //If the connection is not trusted, then ignore header parsing diff --git a/lib/Plugins.Essentials/src/Middleware/HttpMiddlewareResult.cs b/lib/Plugins.Essentials/src/Middleware/HttpMiddlewareResult.cs deleted file mode 100644 index 6054a6e..0000000 --- a/lib/Plugins.Essentials/src/Middleware/HttpMiddlewareResult.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials -* File: HttpMiddlewareResult.cs -* -* HttpMiddlewareResult.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/. -*/ - -namespace VNLib.Plugins.Essentials.Middleware -{ - /// <summary> - /// The result of a <see cref="IHttpMiddleware"/> process. - /// </summary> - public enum HttpMiddlewareResult - { - /// <summary> - /// The request has not been completed and should continue to be processed. - /// </summary> - Continue, - - /// <summary> - /// The request has been handled and no further processing should occur. - /// </summary> - Complete - } -} diff --git a/lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs b/lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs index 4485c55..3c56866 100644 --- a/lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs +++ b/lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs @@ -34,8 +34,8 @@ namespace VNLib.Plugins.Essentials.Middleware public interface IHttpMiddleware { /// <summary> - /// Processes the <see cref="HttpEntity"/> and returns a <see cref="HttpMiddlewareResult"/> - /// indicating whether the request should continue to be processed. + /// Processes the <see cref="HttpEntity"/> and returns a <see cref="FileProcessArgs"/> + /// indicating the result of the process operation /// </summary> /// <param name="entity">The entity to process</param> /// <returns>The result of the operation</returns> diff --git a/lib/Utils.Memory/NativeHeapApi/src/NativeHeapApi.h b/lib/Utils.Memory/NativeHeapApi/src/NativeHeapApi.h index d70745b..6f9ad63 100644 --- a/lib/Utils.Memory/NativeHeapApi/src/NativeHeapApi.h +++ b/lib/Utils.Memory/NativeHeapApi/src/NativeHeapApi.h @@ -43,10 +43,15 @@ #ifdef WIN32 #define HEAP_METHOD_EXPORT __declspec(dllexport) #else - #define METHOD_EXPORT + #define HEAP_METHOD_EXPORT __attribute__((visibility("default"))) #endif #endif // HEAP_METHOD_EXPORT! +#ifndef WIN32 +typedef unsigned long DWORD; +typedef void* LPVOID; +#endif // !WIN32 + /// <summary> /// Internal heap creation flags passed to the creation method by the library loader /// </summary> diff --git a/lib/Utils.Memory/vnlib_rpmalloc/Taskfile.yaml b/lib/Utils.Memory/vnlib_rpmalloc/Taskfile.yaml index d7d921a..635c006 100644 --- a/lib/Utils.Memory/vnlib_rpmalloc/Taskfile.yaml +++ b/lib/Utils.Memory/vnlib_rpmalloc/Taskfile.yaml @@ -55,7 +55,6 @@ tasks: #release dll - cd build/Release && tar -czf '../../bin/win-x64-release-{{.PROJECT_NAME}}.tgz' {{.PROJECT_NAME}}.dll {{.TAR_FILES}} - licenses: cmds: #add rpmalloc license to binary output @@ -65,7 +64,6 @@ tasks: #add readme file - powershell -Command "Copy-Item -Path ./build.readme.txt -Destination '{{.TARGET}}/readme.txt'" - clean: ignore_error: true cmds: diff --git a/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c b/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c index 8a3e65f..20e1554 100644 --- a/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c +++ b/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c @@ -22,12 +22,13 @@ * along with vnlib_rpmalloc. If not, see http://www.gnu.org/licenses/. */ - #include "vnlib_rpmalloc.h" #include <NativeHeapApi.h> #include <rpmalloc.h> #include <stdint.h> +#if defined(_WIN32) + BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { (void)hModule; @@ -54,6 +55,79 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv return TRUE; } +#else + +/* +* Nuts and bolts from rpmalloc/malloc.c for pthread hooks +* for thread and process heap initializations +*/ + +#include <pthread.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> + +//! Set main thread ID (from rpmalloc.c) +extern void rpmalloc_set_main_thread(void); + +static pthread_key_t destructor_key; + +static void thread_destructor(void*); + +static void __attribute__((constructor)) initializer(void) { + rpmalloc_set_main_thread(); + rpmalloc_initialize(); + pthread_key_create(&destructor_key, thread_destructor); +} + +static void __attribute__((destructor)) finalizer(void) { + rpmalloc_finalize(); +} + +typedef struct { + void* (*real_start)(void*); + void* real_arg; +} thread_starter_arg; + +static void* thread_starter(void* argptr) { + thread_starter_arg* arg = argptr; + void* (*real_start)(void*) = arg->real_start; + void* real_arg = arg->real_arg; + rpmalloc_thread_initialize(); + rpfree(argptr); + pthread_setspecific(destructor_key, (void*)1); + return (*real_start)(real_arg); +} + +static void thread_destructor(void* value) { + (void)sizeof(value); + rpmalloc_thread_finalize(1); +} + + +#include <dlfcn.h> + +int pthread_create(pthread_t* thread, + const pthread_attr_t* attr, + void* (*start_routine)(void*), + void* arg) { +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || \ + defined(__APPLE__) || defined(__HAIKU__) + char fname[] = "pthread_create"; +#else + char fname[] = "_pthread_create"; +#endif + void* real_pthread_create = dlsym(RTLD_NEXT, fname); + + rpmalloc_thread_initialize(); + thread_starter_arg* starter_arg = rpmalloc(sizeof(thread_starter_arg)); + starter_arg->real_start = start_routine; + starter_arg->real_arg = arg; + return (*(int (*)(pthread_t*, const pthread_attr_t*, void* (*)(void*), void*))real_pthread_create)(thread, attr, thread_starter, starter_arg); +} + +#endif + #define GLOBAL_HEAP_HANDLE_VALUE -10 #define GLOBAL_HEAP_INIT_CHECK if (!rpmalloc_is_thread_initialized()) { rpmalloc_thread_initialize(); } diff --git a/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.h b/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.h index 0b89580..dd46c91 100644 --- a/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.h +++ b/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.h @@ -3,9 +3,23 @@ #ifndef VNLIB_RPMALLOC_H #if defined(_WIN32) || defined(_WIN64) + #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files #include <windows.h> + +#else + +#define _GNU_SOURCE // for RTLD_NEXT + +#include <stddef.h> + +#define TRUE 1 +#define FALSE 0 + +//Windows type aliases for non-win +typedef int BOOL; + #endif #endif // !VNLIB_RPMALLOC_H
\ No newline at end of file diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs index 32bb3d4..6525db4 100644 --- a/lib/Utils/src/Extensions/MemoryExtensions.cs +++ b/lib/Utils/src/Extensions/MemoryExtensions.cs @@ -438,6 +438,27 @@ namespace VNLib.Utils.Extensions } /// <summary> + /// Allocates a buffer from the current heap and initialzies it by copying the initial data buffer + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="heap"></param> + /// <param name="initialData">The initial data to set the buffer to</param> + /// <returns>The initalized <see cref="MemoryHandle{T}"/> block</returns> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlyMemory<T> initialData) where T : unmanaged + { + //Aloc block + MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length); + + //Copy initial data + MemoryUtil.Copy(initialData, handle, 0); + + return handle; + } + + /// <summary> /// Copies data from the input buffer to the current handle and resizes the handle to the /// size of the buffer /// </summary> diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs index d97036d..eed4ca2 100644 --- a/lib/Utils/src/IO/VnMemoryStream.cs +++ b/lib/Utils/src/IO/VnMemoryStream.cs @@ -94,7 +94,7 @@ namespace VNLib.Utils.IO /// buffer of the specified size on the specified heap to avoid resizing. /// </summary> /// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param> - /// <param name="bufferSize">Number of bytes (length) of the stream if known</param> + /// <param name="bufferSize">The initial internal buffer size, does not effect the length/size of the stream, helps pre-alloc</param> /// <param name="zero">Zero memory allocations during buffer expansions</param> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ArgumentNullException"></exception> @@ -119,7 +119,22 @@ namespace VNLib.Utils.IO _length = data.Length; _position = 0; } - + + /// <summary> + /// Creates a new memory stream from the data provided + /// </summary> + /// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param> + /// <param name="data">Initial data</param> + public VnMemoryStream(IUnmangedHeap heap, ReadOnlyMemory<byte> data) + { + _ = heap ?? throw new ArgumentNullException(nameof(heap)); + //Alloc the internal buffer to match the data stream + _buffer = heap.AllocAndCopy(data); + //Set length + _length = data.Length; + _position = 0; + } + /// <summary> /// WARNING: Dangerous constructor, make sure read-only and owns hanlde are set accordingly /// </summary> @@ -302,6 +317,23 @@ namespace VNLib.Utils.IO return bytesToRead; } + ///<inheritdoc/> + public override unsafe int ReadByte() + { + if (LenToPosDiff == 0) + { + return -1; + } + + //get the value at the current position + byte* ptr = _buffer.GetOffset(_position); + + //Increment position + _position++; + + //Return value + return ptr[0]; + } /* * Async reading will always run synchronously in a memory stream, @@ -469,7 +501,7 @@ namespace VNLib.Utils.IO /// If the current stream is a readonly stream, creates a shallow copy for reading only. /// </summary> /// <returns>New stream shallow copy of the internal stream</returns> - /// <exception cref="InvalidOperationException"></exception> + /// <exception cref="NotSupportedException"></exception> public object Clone() => GetReadonlyShallowCopy(); /* diff --git a/lib/Utils/src/Memory/Caching/ObjectRentalBase.cs b/lib/Utils/src/Memory/Caching/ObjectRentalBase.cs index 305d93f..ca07885 100644 --- a/lib/Utils/src/Memory/Caching/ObjectRentalBase.cs +++ b/lib/Utils/src/Memory/Caching/ObjectRentalBase.cs @@ -39,8 +39,9 @@ namespace VNLib.Utils.Memory.Caching public static ObjectRental<TNew> Create<TNew>(int quota = 0) where TNew : class, new() { static TNew constructor() => new(); - return new ObjectRental<TNew>(constructor, null, null, quota); + return Create(constructor, null, null, quota); } + /// <summary> /// Creates a new <see cref="ObjectRental{T}"/> store with generic rental and return callback handlers /// </summary> @@ -50,18 +51,17 @@ namespace VNLib.Utils.Memory.Caching public static ObjectRental<TNew> Create<TNew>(Action<TNew>? rentCb, Action<TNew>? returnCb, int quota = 0) where TNew : class, new() { static TNew constructor() => new(); - return new ObjectRental<TNew>(constructor, rentCb, returnCb, quota); + return Create(constructor, rentCb, returnCb, quota); } + /// <summary> /// Creates a new <see cref="ObjectRental{T}"/> store with a generic constructor function /// </summary> /// <param name="constructor">The function invoked to create a new instance when required</param> /// <param name="quota">The maximum number of elements that will be cached</param> /// <returns></returns> - public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, int quota = 0) where TNew: class - { - return new ObjectRental<TNew>(constructor, null, null, quota); - } + public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, int quota = 0) where TNew : class => Create(constructor, null, null, quota); + /// <summary> /// Creates a new <see cref="ObjectRental{T}"/> store with generic rental and return callback handlers /// </summary> @@ -69,10 +69,8 @@ namespace VNLib.Utils.Memory.Caching /// <param name="rentCb">Function responsible for preparing an instance to be rented</param> /// <param name="returnCb">Function responsible for cleaning up an instance before reuse</param> /// <param name="quota">The maximum number of elements that will be cached</param> - public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb, int quota = 0) where TNew : class - { - return new ObjectRental<TNew>(constructor, rentCb, returnCb, quota); - } + public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb, int quota = 0) where TNew : class + => new(constructor, rentCb, returnCb, quota); /// <summary> /// Creates a new <see cref="ThreadLocalObjectStorage{TNew}"/> store with generic rental and return callback handlers @@ -82,10 +80,9 @@ namespace VNLib.Utils.Memory.Caching /// <param name="rentCb">Function responsible for preparing an instance to be rented</param> /// <param name="returnCb">Function responsible for cleaning up an instance before reuse</param> /// <returns>The initialized store</returns> - public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb) where TNew : class - { - return new ThreadLocalObjectStorage<TNew>(constructor, rentCb, returnCb); - } + public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb) where TNew : class + => new (constructor, rentCb, returnCb); + /// <summary> /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> store with generic rental and return callback handlers /// </summary> @@ -94,62 +91,75 @@ namespace VNLib.Utils.Memory.Caching public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Action<TNew>? rentCb, Action<TNew>? returnCb) where TNew : class, new() { static TNew constructor() => new(); - return new ThreadLocalObjectStorage<TNew>(constructor, rentCb, returnCb); + return CreateThreadLocal(constructor, rentCb, returnCb); } + /// <summary> /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> store /// </summary> public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>() where TNew : class, new() { static TNew constructor() => new(); - return new ThreadLocalObjectStorage<TNew>(constructor, null, null); + return CreateThreadLocal(constructor, null, null); } + /// <summary> /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> store with a generic constructor function /// </summary> /// <param name="constructor">The function invoked to create a new instance when required</param> /// <returns></returns> - public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor) where TNew : class - { - return new ThreadLocalObjectStorage<TNew>(constructor, null, null); - } + public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor) where TNew : class => CreateThreadLocal(constructor, null, null); /// <summary> - /// Creates a new <see cref="ReusableStore{T}"/> instance with a parameterless constructor + /// Creates a new <see cref="ObjectRental{T}"/> instance with a parameterless constructor /// </summary> /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam> /// <param name="quota">The maximum number of elements that will be cached</param> /// <returns></returns> - public static ReusableStore<T> CreateReusable<T>(int quota = 0) where T : class, IReusable, new() + public static ObjectRental<T> CreateReusable<T>(int quota = 0) where T : class, IReusable, new() { static T constructor() => new(); - return new(constructor, quota); + return CreateReusable(constructor, quota); } + /// <summary> - /// Creates a new <see cref="ReusableStore{T}"/> instance with the specified constructor + /// Creates a new <see cref="ObjectRental{T}"/> instance with the specified constructor /// </summary> /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam> /// <param name="constructor">The constructor function invoked to create new instances of the <see cref="IReusable"/> type</param> /// <param name="quota">The maximum number of elements that will be cached</param> /// <returns></returns> - public static ReusableStore<T> CreateReusable<T>(Func<T> constructor, int quota = 0) where T : class, IReusable => new(constructor, quota); + public static ObjectRental<T> CreateReusable<T>(Func<T> constructor, int quota = 0) where T : class, IReusable + { + //Rent/return callbacks + static void Rent(T item) => item.Prepare(); + static void Return(T item) => item.Release(); + + return Create(constructor, Rent, Return, quota); + } /// <summary> - /// Creates a new <see cref="ThreadLocalReusableStore{T}"/> instance with a parameterless constructor + /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> instance with a parameterless constructor /// </summary> /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam> /// <returns></returns> - public static ThreadLocalReusableStore<T> CreateThreadLocalReusable<T>() where T : class, IReusable, new() + public static ThreadLocalObjectStorage<T> CreateThreadLocalReusable<T>() where T : class, IReusable, new() { static T constructor() => new(); - return new(constructor); + return CreateThreadLocalReusable(constructor); } + /// <summary> - /// Creates a new <see cref="ThreadLocalReusableStore{T}"/> instance with the specified constructor + /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> instance with the specified constructor /// </summary> /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam> /// <param name="constructor">The constructor function invoked to create new instances of the <see cref="IReusable"/> type</param> /// <returns></returns> - public static ThreadLocalReusableStore<T> CreateThreadLocalReusable<T>(Func<T> constructor) where T : class, IReusable => new(constructor); + public static ThreadLocalObjectStorage<T> CreateThreadLocalReusable<T>(Func<T> constructor) where T : class, IReusable + { + static void Rent(T item) => item.Prepare(); + static void Return(T item) => item.Release(); + return new ThreadLocalObjectStorage<T>(constructor, Rent, Return); + } } } diff --git a/lib/Utils/src/Memory/Caching/ReusableStore.cs b/lib/Utils/src/Memory/Caching/ReusableStore.cs deleted file mode 100644 index aacd012..0000000 --- a/lib/Utils/src/Memory/Caching/ReusableStore.cs +++ /dev/null @@ -1,61 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: ReusableStore.cs -* -* ReusableStore.cs is part of VNLib.Utils which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Utils 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.Utils 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.Utils. If not, see http://www.gnu.org/licenses/. -*/ - -using System; - -namespace VNLib.Utils.Memory.Caching -{ - - /// <summary> - /// A reusable object store that extends <see cref="ObjectRental{T}"/>, that allows for objects to be reused heavily - /// </summary> - /// <typeparam name="T">A reusable object</typeparam> - public class ReusableStore<T> : ObjectRental<T> where T : class, IReusable - { - internal ReusableStore(Func<T> constructor, int quota) :base(constructor, null, null, quota) - {} - ///<inheritdoc/> - public override T Rent() - { - //Rent the object (or create it) - T rental = base.Rent(); - //Invoke prepare function - rental.Prepare(); - //return object - return rental; - } - ///<inheritdoc/> - public override void Return(T item) - { - /* - * Clean up the item by invoking the cleanup function, - * and only return the item for reuse if the caller allows - */ - if (item.Release()) - { - base.Return(item); - } - } - } -}
\ No newline at end of file diff --git a/lib/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs b/lib/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs deleted file mode 100644 index 83cd4d6..0000000 --- a/lib/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs +++ /dev/null @@ -1,64 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: ThreadLocalReusableStore.cs -* -* ThreadLocalReusableStore.cs is part of VNLib.Utils which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Utils 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.Utils 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.Utils. If not, see http://www.gnu.org/licenses/. -*/ - -using System; - -namespace VNLib.Utils.Memory.Caching -{ - /// <summary> - /// A reusable object store that extends <see cref="ThreadLocalObjectStorage{T}"/>, that allows for objects to be reused heavily - /// in a thread-local cache - /// </summary> - /// <typeparam name="T">A reusable object</typeparam> - public class ThreadLocalReusableStore<T> : ThreadLocalObjectStorage<T> where T: class, IReusable - { - /// <summary> - /// Creates a new <see cref="ThreadLocalReusableStore{T}"/> instance - /// </summary> - internal ThreadLocalReusableStore(Func<T> constructor):base(constructor, null, null) - { } - ///<inheritdoc/> - public override T Rent() - { - //Rent the object (or create it) - T rental = base.Rent(); - //Invoke prepare function - rental.Prepare(); - //return object - return rental; - } - ///<inheritdoc/> - public override void Return(T item) - { - /* - * Clean up the item by invoking the cleanup function, - * and only return the item for reuse if the caller allows - */ - if (item.Release()) - { - base.Return(item); - } - } - } -}
\ No newline at end of file diff --git a/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs b/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs index 41b08c1..820c819 100644 --- a/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs +++ b/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs @@ -34,6 +34,7 @@ namespace VNLib.Utils.Memory.Diagnostics public class TrackedHeapWrapper : VnDisposeable, IUnmangedHeap { private readonly IUnmangedHeap _heap; + private readonly bool _ownsHeap; private readonly object _statsLock; private readonly ConcurrentDictionary<IntPtr, ulong> _table; @@ -62,13 +63,15 @@ namespace VNLib.Utils.Memory.Diagnostics /// Creates a new diagnostics wrapper for the heap /// </summary> /// <param name="heap">The heap to gather statistics on</param> - public TrackedHeapWrapper(IUnmangedHeap heap) + /// <param name="ownsHeap">If true, the wrapper will dispose the heap when disposed</param> + public TrackedHeapWrapper(IUnmangedHeap heap, bool ownsHeap) { _statsLock = new(); _table = new(); _heap = heap; - //Default min block size to 0 + //Default min block size to max _minBlockSize = ulong.MaxValue; + _ownsHeap = ownsHeap; } /// <summary> @@ -124,8 +127,11 @@ namespace VNLib.Utils.Memory.Diagnostics ///<inheritdoc/> protected override void Free() { - Heap.Dispose(); - } + if(_ownsHeap) + { + _heap.Dispose(); + } + } ///<inheritdoc/> public bool Free(ref IntPtr block) diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index f671da0..dc69e76 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -68,6 +68,11 @@ namespace VNLib.Utils.Memory public const string SHARED_HEAP_RAW_FLAGS = "VNLIB_SHARED_HEAP_RAW_FLAGS"; /// <summary> + /// The environment variable name used to specify the shared heap type + /// </summary> + public const string SHARED_HEAP_GLOBAL_ZERO = "VNLIB_SHARED_HEAP_GLOBAL_ZERO"; + + /// <summary> /// Initial shared heap size (bytes) /// </summary> public const nuint SHARED_HEAP_INIT_SIZE = 20971520; @@ -102,10 +107,12 @@ namespace VNLib.Utils.Memory { //Get env for heap diag _ = ERRNO.TryParse(Environment.GetEnvironmentVariable(SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV), out ERRNO diagEnable); + _ = ERRNO.TryParse(Environment.GetEnvironmentVariable(SHARED_HEAP_GLOBAL_ZERO), out ERRNO globalZero); Trace.WriteIf(diagEnable, "Shared heap diagnostics enabled"); + Trace.WriteIf(globalZero, "Shared heap global zero enabled"); - Lazy<IUnmangedHeap> heap = new (() => InitHeapInternal(true, diagEnable), LazyThreadSafetyMode.PublicationOnly); + Lazy<IUnmangedHeap> heap = new (() => InitHeapInternal(true, diagEnable, globalZero), LazyThreadSafetyMode.PublicationOnly); //Cleanup the heap on process exit AppDomain.CurrentDomain.DomainUnload += DomainUnloaded; @@ -143,12 +150,13 @@ namespace VNLib.Utils.Memory /// Initializes a new <see cref="IUnmangedHeap"/> determined by compilation/runtime flags /// and operating system type for the current proccess. /// </summary> + /// <param name="globalZero">If true, sets the <see cref="HeapCreation.GlobalZero"/> flag</param> /// <returns>An <see cref="IUnmangedHeap"/> for the current process</returns> /// <exception cref="SystemException"></exception> /// <exception cref="DllNotFoundException"></exception> - public static IUnmangedHeap InitializeNewHeapForProcess() => InitHeapInternal(false, false); + public static IUnmangedHeap InitializeNewHeapForProcess(bool globalZero = false) => InitHeapInternal(false, false, globalZero); - private static IUnmangedHeap InitHeapInternal(bool isShared, bool enableStats) + private static IUnmangedHeap InitHeapInternal(bool isShared, bool enableStats, bool globalZero) { bool IsWindows = OperatingSystem.IsWindows(); @@ -167,6 +175,9 @@ namespace VNLib.Utils.Memory */ cFlags |= isShared ? HeapCreation.IsSharedHeap : HeapCreation.None; + //Set global zero flag if requested + cFlags |= globalZero ? HeapCreation.GlobalZero : HeapCreation.None; + IUnmangedHeap heap; ERRNO userFlags = 0; @@ -207,7 +218,7 @@ namespace VNLib.Utils.Memory } //Enable heap statistics - return enableStats ? new TrackedHeapWrapper(heap) : heap; + return enableStats ? new TrackedHeapWrapper(heap, true) : heap; } /// <summary> @@ -230,7 +241,7 @@ namespace VNLib.Utils.Memory { return; } - + uint byteSize = ByteCount<T>((uint)block.Length); fixed (void* ptr = &MemoryMarshal.GetReference(block)) @@ -305,6 +316,14 @@ namespace VNLib.Utils.Memory } /// <summary> + /// Zeroes a block of memory of the given unmanaged type + /// </summary> + /// <typeparam name="T">The unmanaged type to zero</typeparam> + /// <param name="block">A pointer to the block of memory to zero</param> + /// <param name="itemCount">The number of elements in the block to zero</param> + public static void InitializeBlock<T>(IntPtr block, int itemCount) where T : unmanaged => InitializeBlock((T*)block, itemCount); + + /// <summary> /// Zeroes a block of memory pointing to the structure /// </summary> /// <typeparam name="T">The structure type</typeparam> @@ -498,7 +517,7 @@ namespace VNLib.Utils.Memory CheckBounds(dest, destOffset, count); //Check if 64bit - if(sizeof(nuint) == 8) + if(sizeof(void*) == 8) { //Get the number of bytes to copy nuint byteCount = ByteCount<T>(count); @@ -510,7 +529,7 @@ namespace VNLib.Utils.Memory T* src = (T*)srcHandle.Pointer + offset; //pin array - fixed (T* dst = dest) + fixed (T* dst = &MemoryMarshal.GetArrayDataReference(dest)) { //Offset dest ptr T* dstOffset = dst + destOffset; @@ -592,7 +611,7 @@ namespace VNLib.Utils.Memory { if (offset + count > handle.Length) { - throw new ArgumentOutOfRangeException("The offset or count is outside of the range of the block of memory"); + throw new ArgumentOutOfRangeException(nameof(offset), "Offset or count are beyond the range of the supplied memory handle"); } } diff --git a/lib/Utils/src/VnEncoding.cs b/lib/Utils/src/VnEncoding.cs index b8f18bd..e945135 100644 --- a/lib/Utils/src/VnEncoding.cs +++ b/lib/Utils/src/VnEncoding.cs @@ -28,6 +28,7 @@ using System.Text; using System.Buffers; using System.Text.Json; using System.Threading; +using System.Diagnostics; using System.Buffers.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; @@ -37,7 +38,6 @@ using VNLib.Utils.IO; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; - namespace VNLib.Utils { /// <summary> @@ -778,7 +778,7 @@ namespace VNLib.Utils { return Convert.TryToBase64Chars(buffer, base64, out int charsWritten, options: options) ? charsWritten : ERRNO.E_FAIL; } - + /* * Calc base64 padding chars excluding the length mod 4 = 0 case @@ -983,6 +983,99 @@ namespace VNLib.Utils } /// <summary> + /// Attempts to base64url encode the binary buffer to it's base64url encoded representation + /// in place, aka does not allocate a temporary buffer. The buffer must be large enough to + /// encode the data, if not the operation will fail. The data in this span will be overwritten + /// to do the conversion + /// </summary> + /// <param name="rawData">The raw data buffer that will be used to encode data aswell as read it</param> + /// <param name="length">The length of the binary data to encode</param> + /// <param name="includePadding">A value specifying whether base64 padding should be encoded</param> + /// <returns>The base64url encoded string</returns> + /// <exception cref="ArgumentException"></exception> + public static string ToBase64UrlSafeStringInPlace(Span<byte> rawData, int length, bool includePadding) + { + //Encode in place + if (Base64.EncodeToUtf8InPlace(rawData, length, out int converted) != OperationStatus.Done) + { + throw new ArgumentException("The input buffer was not large enough to encode in-place", nameof(rawData)); + } + + //trim to converted size + Span<byte> base64 = rawData[..converted]; + + //Make url safe + Base64ToUrlSafeInPlace(base64); + + //Remove padding + if (!includePadding) + { + base64 = base64.TrimEnd((byte)0x3d); + } + + //Convert to string + return Encoding.UTF8.GetString(base64); + } + + /// <summary> + /// Converts binary data to it's base64url encoded representation and may allocate a temporary + /// heap buffer. + /// </summary> + /// <param name="rawData">The binary data to encode</param> + /// <param name="includePadding">A value that indicates if the base64 padding characters should be included</param> + /// <returns>The base64url encoded string</returns> + /// <exception cref="ArgumentException"></exception> + public static string ToBase64UrlSafeString(ReadOnlySpan<byte> rawData, bool includePadding) + { + int maxBufSize = Base64.GetMaxEncodedToUtf8Length(rawData.Length); + + if(maxBufSize > MAX_STACKALLOC) + { + //alloc buffer + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage(maxBufSize); + + return ConvertToBase64UrlStringInternal(rawData, buffer.Span, includePadding); + } + else + { + //Stack alloc buffer + Span<byte> buffer = stackalloc byte[maxBufSize]; + + return ConvertToBase64UrlStringInternal(rawData, buffer, includePadding); + } + } + + private static string ConvertToBase64UrlStringInternal(ReadOnlySpan<byte> rawData, Span<byte> buffer, bool includePadding) + { + //Conver to base64 + OperationStatus status = Base64.EncodeToUtf8(rawData, buffer, out _, out int written, true); + + //Check for invalid states + Debug.Assert(status != OperationStatus.DestinationTooSmall, "Buffer allocation was too small for the conversion"); + Debug.Assert(status != OperationStatus.NeedMoreData, "Need more data status was returned but is not valid for an encoding operation"); + + //Should never occur, but just in case, this is an input error + if (status == OperationStatus.InvalidData) + { + throw new ArgumentException("Your input data contained values that could not be converted to base64", nameof(rawData)); + } + + Span<byte> base64 = buffer[..written]; + + //Make url safe + Base64ToUrlSafeInPlace(base64); + + //Remove padding + if (!includePadding) + { + base64 = base64.TrimEnd((byte)0x3d); + } + + //Convert to string + return Encoding.UTF8.GetString(base64); + } + + /// <summary> /// Encodes the binary input buffer to its base64url safe utf8 encoding, and writes the output /// to the supplied buffer. Be sure to call <see cref="Base64.GetMaxEncodedToUtf8Length(int)"/> /// to allocate the correct size buffer for encoding diff --git a/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs b/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs index 7119d21..3c9bde7 100644 --- a/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs +++ b/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs @@ -2,12 +2,12 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Linq; using System.Threading; +using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Linq; namespace VNLib.Utils.Async.Tests { @@ -101,7 +101,7 @@ namespace VNLib.Utils.Async.Tests int maxCount = 64; - Task[] asyncArr = new int[maxCount].Select(async p => + Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () => { //Take a lock then random delay, then release Task entry = serializer.WaitAsync(DEFAULT_KEY); @@ -119,7 +119,7 @@ namespace VNLib.Utils.Async.Tests serializer.Release(DEFAULT_KEY); - }).ToArray(); + })).ToArray(); Task.WaitAll(asyncArr); } @@ -149,7 +149,7 @@ namespace VNLib.Utils.Async.Tests using CancellationTokenSource cts = new(500); - Task[] asyncArr = new int[maxCount].Select(async p => + Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () => { //Take a lock then random delay, then release await serializer.WaitAsync(DEFAULT_KEY, cts.Token); @@ -159,7 +159,7 @@ namespace VNLib.Utils.Async.Tests serializer.Release(DEFAULT_KEY); - }).ToArray(); + })).ToArray(); Task.WaitAll(asyncArr); @@ -175,18 +175,19 @@ namespace VNLib.Utils.Async.Tests //Alloc serailzer base on string IAsyncAccessSerializer<string> serializer = new AsyncAccessSerializer<string>(100, 100, StringComparer.Ordinal); - int maxCount = 128; + const int maxCount = 128; + const int itterations = 20; string test = ""; Stopwatch timer = new(); using CancellationTokenSource cts = new(500); - for (int i = 0; i < 10; i++) + for (int i = 0; i < itterations; i++) { test = ""; timer.Restart(); - Task[] asyncArr = new int[maxCount].Select(async p => + Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () => { //Take a lock then random delay, then release await serializer.WaitAsync(DEFAULT_KEY, cts.Token); @@ -196,7 +197,7 @@ namespace VNLib.Utils.Async.Tests serializer.Release(DEFAULT_KEY); - }).ToArray(); + })).ToArray(); Task.WaitAll(asyncArr); @@ -208,12 +209,12 @@ namespace VNLib.Utils.Async.Tests using SemaphoreSlim slim = new(1,1); - for (int i = 0; i < 10; i++) + for (int i = 0; i < itterations; i++) { test = ""; timer.Restart(); - Task[] asyncArr = new int[maxCount].Select(async p => + Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () => { //Take a lock then random delay, then release await slim.WaitAsync(cts.Token); @@ -222,7 +223,7 @@ namespace VNLib.Utils.Async.Tests test += "0"; slim.Release(); - }).ToArray(); + })).ToArray(); Task.WaitAll(asyncArr); diff --git a/lib/Utils/tests/IO/VnMemoryStreamTests.cs b/lib/Utils/tests/IO/VnMemoryStreamTests.cs new file mode 100644 index 0000000..3eb95ce --- /dev/null +++ b/lib/Utils/tests/IO/VnMemoryStreamTests.cs @@ -0,0 +1,99 @@ +using System; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; + +namespace VNLib.Utils.IO.Tests +{ + [TestClass()] + public class VnMemoryStreamTests + { + [TestMethod()] + public void VnMemoryStreamConstructorTest() + { + using (VnMemoryStream vms = new()) + { + Assert.IsTrue(vms.Length == 0); + Assert.IsTrue(vms.Position == 0); + Assert.IsTrue(vms.CanSeek == true); + Assert.IsTrue(vms.CanRead == true); + Assert.IsTrue(vms.CanWrite == true); + } + + //Test heap + IUnmangedHeap privateHeap = MemoryUtil.InitializeNewHeapForProcess(); + + using (VnMemoryStream vms = new(privateHeap, 1024, false)) + { + Assert.IsTrue(vms.Length == 0); + Assert.IsTrue(vms.Position == 0); + Assert.IsTrue(vms.CanSeek == true); + Assert.IsTrue(vms.CanRead == true); + Assert.IsTrue(vms.CanWrite == true); + } + + + //Create from mem handle + MemoryHandle<byte> handle = privateHeap.Alloc<byte>(byte.MaxValue); + + using (VnMemoryStream vms = VnMemoryStream.ConsumeHandle(handle, handle.GetIntLength(), false)) + { + Assert.IsTrue(vms.Length == byte.MaxValue); + Assert.IsTrue(vms.Position == 0); + Assert.IsTrue(vms.CanSeek == true); + Assert.IsTrue(vms.CanRead == true); + Assert.IsTrue(vms.CanWrite == true); + } + + //From existing data + ReadOnlySpan<byte> testSpan = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + using (VnMemoryStream vms = new (privateHeap, testSpan)) + { + Assert.IsTrue(vms.Length == testSpan.Length); + Assert.IsTrue(vms.Position == 0); + + //Check values copied + while (vms.Position < vms.Length) + { + byte test = testSpan[(int)vms.Position]; + Assert.IsTrue(vms.ReadByte() == test); + } + } + + ReadOnlyMemory<byte> testMemory = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + using (VnMemoryStream vms = new (privateHeap, testMemory)) + { + Assert.IsTrue(vms.Length == testMemory.Length); + Assert.IsTrue(vms.Position == 0); + + //Check values copied + while(vms.Position < vms.Length) + { + byte test = testMemory.Span[(int)vms.Position]; + Assert.IsTrue(vms.ReadByte() == test); + } + } + } + + [TestMethod()] + public void VnMemoryStreamReadonlyTest() + { + using VnMemoryStream vms = new(MemoryUtil.Shared, 0, false); + + Assert.IsTrue(vms.CanWrite == true); + + //Convert to readonly + _ = VnMemoryStream.CreateReadonly(vms); + + Assert.IsTrue(vms.CanSeek == true); + Assert.IsTrue(vms.CanRead == true); + Assert.IsTrue(vms.CanWrite == false); + + //Try to write + Assert.ThrowsException<NotSupportedException>(() => vms.WriteByte(0)); + + } + } +}
\ No newline at end of file diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index 2166eea..64f94ff 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -388,7 +388,7 @@ namespace VNLib.Utils.Memory.Tests IUnmangedHeap heap = MemoryUtil.InitializeNewHeapForProcess(); //Init wrapper and dispose - using TrackedHeapWrapper wrapper = new(heap); + using TrackedHeapWrapper wrapper = new(heap, true); //Confirm 0 stats HeapStatistics preTest = wrapper.GetCurrentStats(); |