aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Hashing.Portable/src/Argon2/VnArgon2.cs10
-rw-r--r--lib/Net.Compression/vnlib_compress/compression.c20
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMClient.cs3
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMListener.cs23
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/Construction/HttpServiceStackBuilder.cs22
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/HttpServiceStack.cs3
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IManagedPlugin.cs40
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IManualPlugin.cs77
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/IPluginInitializer.cs43
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ManagedPlugin.cs99
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/PluginManager.cs144
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/PluginStackInitializer.cs329
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/ServiceGroup.cs17
-rw-r--r--lib/Plugins.Essentials/src/Accounts/AccountUtils.cs19
-rw-r--r--lib/Plugins.Essentials/src/Accounts/FailedLoginLockout.cs137
-rw-r--r--lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs141
-rw-r--r--lib/Plugins.Essentials/src/Extensions/ConnectionInfoExtensions.cs36
-rw-r--r--lib/Plugins.Essentials/src/Middleware/HttpMiddlewareResult.cs42
-rw-r--r--lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs4
-rw-r--r--lib/Utils.Memory/NativeHeapApi/src/NativeHeapApi.h7
-rw-r--r--lib/Utils.Memory/vnlib_rpmalloc/Taskfile.yaml2
-rw-r--r--lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c76
-rw-r--r--lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.h14
-rw-r--r--lib/Utils/src/Extensions/MemoryExtensions.cs21
-rw-r--r--lib/Utils/src/IO/VnMemoryStream.cs38
-rw-r--r--lib/Utils/src/Memory/Caching/ObjectRentalBase.cs70
-rw-r--r--lib/Utils/src/Memory/Caching/ReusableStore.cs61
-rw-r--r--lib/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs64
-rw-r--r--lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs14
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs35
-rw-r--r--lib/Utils/src/VnEncoding.cs97
-rw-r--r--lib/Utils/tests/Async/AsyncAccessSerializerTests.cs27
-rw-r--r--lib/Utils/tests/IO/VnMemoryStreamTests.cs99
-rw-r--r--lib/Utils/tests/Memory/MemoryUtilTests.cs2
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();