aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--lib/Plugins.Runtime/src/AsmFileWatcher.cs104
-rw-r--r--lib/Plugins.Runtime/src/AssemblyWatcher.cs81
-rw-r--r--lib/Plugins.Runtime/src/IPluginAssemblyLoadConfig.cs (renamed from lib/Plugins.Runtime/src/IPluginConfig.cs)6
-rw-r--r--lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs2
-rw-r--r--lib/Plugins.Runtime/src/IPluginConfigReader.cs43
-rw-r--r--lib/Plugins.Runtime/src/LoaderExtensions.cs88
-rw-r--r--lib/Plugins.Runtime/src/PluginStackBuilder.cs78
-rw-r--r--lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs13
-rw-r--r--lib/Plugins.Runtime/src/RuntimePluginLoader.cs2
-rw-r--r--lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c30
-rw-r--r--lib/Utils/src/Async/AsyncAccessSerializer.cs277
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs7
-rw-r--r--lib/Utils/tests/.runsettings1
-rw-r--r--lib/Utils/tests/Async/AsyncAccessSerializerTests.cs149
-rw-r--r--lib/Utils/tests/Memory/MemoryUtilTests.cs1
-rw-r--r--lib/Utils/tests/Memory/NativeHeapTests.cs6
-rw-r--r--lib/Utils/tests/VnEncodingTests.cs2
18 files changed, 614 insertions, 279 deletions
diff --git a/README.md b/README.md
index ea81542..0368302 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,6 @@ Again, go to my website below, my email address is available, go ahead and send
## Index/NameSpaces
**VNLib.**
- [Utils](lib/Utils/#) - A mutli-use library focused on reducing complexity for working with native resources, memory, asynchronous patterns and data-structures, object/resource pooling, critical resource handling, and common logging abstractions.
-- [NativeHeapApi](lib/NativeHeapApi/#) - A C implementation overview for building unmanaged heaps for use in the Utils.Memory.NativeHeap umanaged heap architecture.
- [Hashing.Portable](lib/Hashing.Portable/#) - Common cryptographic/hashing relate operations in a single package with room to grow. Also Argon2 bindings
- [Net.Http](lib/Net.Http/#) - High performance ^HTTP/1.1 application processing library, transport not included! For web or custom use, custom app layer protocols supported - roll your own web-sockets or SSE if you want! (See [Essentials](lib/Plugins.Essentials/#) library if you want to use mine! It's not bad.)
- [Net.Transport.SimpleTCP](lib/Net.Transport.SimpleTCP/#) - A not-so-simple, performance oriented, low/no allocation, .NET/C# native, TCP listener library using the System.IO.Pipelines architecture with SSL/TLS support.
@@ -36,7 +35,7 @@ Again, go to my website below, my email address is available, go ahead and send
- [Plugins.Essentials.ServiceStack](lib/Plugins.Essentials.ServiceStack/#) - A library for scaffolding structured web applications from the individual http and supporting libraries into a completely managed http service stack for an entire application.
- [Plugins.PluginBase](lib/Plugins.PluginBase/#) - Base library/api for plugin developers to build fully managed/supported runtime loaded plugins, without worrying about the plumbing, such as the IPlugin api, endpoint creation, and logging! This library is required if you wish to use most of the Plugin.Extensions libraries.
- [Net.Messaging.FBM](lib/Net.Messaging.FBM/#) - Fixed Buffer Messaging protocol, high performance, request/response architecture, client & server library, built atop http and web-sockets. As implied, relies on fixed sized internal buffers that are negotiated to transfer data with minimal overhead for known messaging architectures.
-- [WinRpMalloc](lib/WinRpMalloc/#) - A Windows x64 dll project that exposes the rpmalloc memory allocator as a NativeHeap for .NET Utils library loading in the unmanned heap architecture.
+- [Utils.Memory](lib/Utils.Memory/#) - Utilty libraries for native memory management framework for VNLib, including an x64 CMake build of rpmalloc.
- [Net.Compression](lib/Net.Compression/#) - A cross platform native compression provider and IHttpCompressorManager configured for runtime dynamic loading for high performance native response data compression.
- [Net.Rest.Client](lib/Net.Rest.Client/#) - A minimal library that provides a RestSharp client resource pool for concurrent usage with async support, along with an OAuth2 client credentials IAuthenticator implementation for use with Oauth2 plugins.
diff --git a/lib/Plugins.Runtime/src/AsmFileWatcher.cs b/lib/Plugins.Runtime/src/AsmFileWatcher.cs
deleted file mode 100644
index f2a0ca7..0000000
--- a/lib/Plugins.Runtime/src/AsmFileWatcher.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Runtime
-* File: AsmFileWatcher.cs
-*
-* AsmFileWatcher.cs is part of VNLib.Plugins.Runtime which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Runtime 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.Plugins.Runtime 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.Plugins.Runtime. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System.IO;
-using System.Threading;
-
-using VNLib.Utils;
-using VNLib.Utils.Extensions;
-
-namespace VNLib.Plugins.Runtime
-{
- internal sealed class AsmFileWatcher : VnDisposeable
- {
- public IPluginReloadEventHandler Handler { get; }
-
- private readonly IPluginAssemblyLoader _loaderSource;
- private readonly Timer _delayTimer;
- private readonly FileSystemWatcher _watcher;
-
- private bool _pause;
-
- public AsmFileWatcher(IPluginAssemblyLoader LoaderSource, IPluginReloadEventHandler handler)
- {
- Handler = handler;
- _loaderSource = LoaderSource;
-
- string dir = Path.GetDirectoryName(LoaderSource.Config.AssemblyFile)!;
-
- //Configure watcher to notify only when the assembly file changes
- _watcher = new FileSystemWatcher(dir)
- {
- Filter = "*.dll",
- EnableRaisingEvents = false,
- IncludeSubdirectories = true,
- NotifyFilter = NotifyFilters.LastWrite,
- };
-
- //Configure listener
- _watcher.Changed += OnFileChanged;
- _watcher.Created += OnFileChanged;
-
- _watcher.EnableRaisingEvents = true;
-
- //setup delay timer to wait on the config
- _delayTimer = new(OnTimeout, this, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
- }
-
- void OnFileChanged(object sender, FileSystemEventArgs e)
- {
- //if were already waiting to process an event, we dont need to stage another
- if (_pause)
- {
- return;
- }
-
- //Set pause flag
- _pause = true;
-
- //Restart the timer to trigger reload event on elapsed
- _delayTimer.Restart(_loaderSource.Config.ReloadDelay);
- }
-
- private void OnTimeout(object? state)
- {
- _delayTimer.Stop();
-
- //Fire event, let exception crash app
- Handler.OnPluginUnloaded(_loaderSource);
-
- //Clear pause flag
- _pause = false;
- }
-
- protected override void Free()
- {
- _delayTimer.Dispose();
-
- //Detach event handler and dispose watcher
- _watcher.Changed -= OnFileChanged;
- _watcher.Dispose();
- }
- }
-}
diff --git a/lib/Plugins.Runtime/src/AssemblyWatcher.cs b/lib/Plugins.Runtime/src/AssemblyWatcher.cs
index 1cf87b0..09b49dc 100644
--- a/lib/Plugins.Runtime/src/AssemblyWatcher.cs
+++ b/lib/Plugins.Runtime/src/AssemblyWatcher.cs
@@ -22,8 +22,13 @@
* along with VNLib.Plugins.Runtime. If not, see http://www.gnu.org/licenses/.
*/
+using System.IO;
+using System.Threading;
using System.Collections.Generic;
+using VNLib.Utils;
+using VNLib.Utils.Extensions;
+
namespace VNLib.Plugins.Runtime
{
internal sealed class AssemblyWatcher : IPluginAssemblyWatcher
@@ -36,6 +41,7 @@ namespace VNLib.Plugins.Runtime
_watchers = new();
}
+ ///<inheritdoc/>
public void StopWatching(IPluginReloadEventHandler handler)
{
lock (_lock)
@@ -49,6 +55,7 @@ namespace VNLib.Plugins.Runtime
}
}
+ ///<inheritdoc/>
public void WatchAssembly(IPluginReloadEventHandler handler, IPluginAssemblyLoader loader)
{
lock(_lock)
@@ -65,6 +72,78 @@ namespace VNLib.Plugins.Runtime
//Store watcher
_watchers.Add(handler, watcher);
}
- }
+ }
+
+ private sealed class AsmFileWatcher : VnDisposeable
+ {
+ public IPluginReloadEventHandler Handler { get; }
+
+ private readonly IPluginAssemblyLoader _loaderSource;
+ private readonly Timer _delayTimer;
+ private readonly FileSystemWatcher _watcher;
+
+ private bool _pause;
+
+ public AsmFileWatcher(IPluginAssemblyLoader LoaderSource, IPluginReloadEventHandler handler)
+ {
+ Handler = handler;
+ _loaderSource = LoaderSource;
+
+ string dir = Path.GetDirectoryName(LoaderSource.Config.AssemblyFile)!;
+
+ //Configure watcher to notify only when the assembly file changes
+ _watcher = new FileSystemWatcher(dir)
+ {
+ Filter = "*.dll",
+ EnableRaisingEvents = false,
+ IncludeSubdirectories = true,
+ NotifyFilter = NotifyFilters.LastWrite,
+ };
+
+ //Configure listener
+ _watcher.Changed += OnFileChanged;
+ _watcher.Created += OnFileChanged;
+
+ _watcher.EnableRaisingEvents = true;
+
+ //setup delay timer to wait on the config
+ _delayTimer = new(OnTimeout, this, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
+ }
+
+ void OnFileChanged(object sender, FileSystemEventArgs e)
+ {
+ //if were already waiting to process an event, we dont need to stage another
+ if (_pause)
+ {
+ return;
+ }
+
+ //Set pause flag
+ _pause = true;
+
+ //Restart the timer to trigger reload event on elapsed
+ _delayTimer.Restart(_loaderSource.Config.ReloadDelay);
+ }
+
+ private void OnTimeout(object? state)
+ {
+ _delayTimer.Stop();
+
+ //Fire event, let exception crash app
+ Handler.OnPluginUnloaded(_loaderSource);
+
+ //Clear pause flag
+ _pause = false;
+ }
+
+ protected override void Free()
+ {
+ _delayTimer.Dispose();
+
+ //Detach event handler and dispose watcher
+ _watcher.Changed -= OnFileChanged;
+ _watcher.Dispose();
+ }
+ }
}
}
diff --git a/lib/Plugins.Runtime/src/IPluginConfig.cs b/lib/Plugins.Runtime/src/IPluginAssemblyLoadConfig.cs
index a460fc0..4b5996e 100644
--- a/lib/Plugins.Runtime/src/IPluginConfig.cs
+++ b/lib/Plugins.Runtime/src/IPluginAssemblyLoadConfig.cs
@@ -3,9 +3,9 @@
*
* Library: VNLib
* Package: VNLib.Plugins.Runtime
-* File: IPluginConfig.cs
+* File: IPluginAssemblyLoadConfig.cs
*
-* IPluginConfig.cs is part of VNLib.Plugins.Runtime which is part
+* IPluginAssemblyLoadConfig.cs is part of VNLib.Plugins.Runtime which is part
* of the larger VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Runtime is free software: you can redistribute it and/or modify
@@ -31,7 +31,7 @@ namespace VNLib.Plugins.Runtime
/// Represents configuration information for a <see cref="IPluginAssemblyLoader"/>
/// instance.
/// </summary>
- public interface IPluginConfig
+ public interface IPluginAssemblyLoadConfig
{
/// <summary>
/// A value that indicates if the instance is unlodable.
diff --git a/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs b/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs
index d75ac47..7767473 100644
--- a/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs
+++ b/lib/Plugins.Runtime/src/IPluginAssemblyLoader.cs
@@ -34,6 +34,6 @@ namespace VNLib.Plugins.Runtime
/// <summary>
/// Gets the plugin's configuration information
/// </summary>
- IPluginConfig Config { get; }
+ IPluginAssemblyLoadConfig Config { get; }
}
}
diff --git a/lib/Plugins.Runtime/src/IPluginConfigReader.cs b/lib/Plugins.Runtime/src/IPluginConfigReader.cs
new file mode 100644
index 0000000..d50e0ab
--- /dev/null
+++ b/lib/Plugins.Runtime/src/IPluginConfigReader.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Runtime
+* File: IPluginConfigReader.cs
+*
+* IPluginConfigReader.cs is part of VNLib.Plugins.Runtime which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Runtime 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.Plugins.Runtime 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.Plugins.Runtime. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System.IO;
+
+namespace VNLib.Plugins.Runtime
+{
+ /// <summary>
+ /// Represents an object that gets configuration data for the desired assembly configuration
+ /// and writes that conifguration to the output stream.
+ /// </summary>
+ public interface IPluginConfigReader
+ {
+ /// <summary>
+ /// Gets the configuration data for the desired assembly configuration and writes that
+ /// configuration to the output stream.
+ /// </summary>
+ /// <param name="asmConfig">The assembly configuration to get the configuration data for</param>
+ /// <param name="outputStream">The stream to write the configuration file data to</param>
+ void ReadPluginConfigData(IPluginAssemblyLoadConfig asmConfig, Stream outputStream);
+ }
+}
diff --git a/lib/Plugins.Runtime/src/LoaderExtensions.cs b/lib/Plugins.Runtime/src/LoaderExtensions.cs
index 4dc1253..c553f4b 100644
--- a/lib/Plugins.Runtime/src/LoaderExtensions.cs
+++ b/lib/Plugins.Runtime/src/LoaderExtensions.cs
@@ -25,6 +25,7 @@
using System;
using System.IO;
using System.Linq;
+using System.Text;
using System.Text.Json;
using System.Collections.Generic;
@@ -259,24 +260,40 @@ namespace VNLib.Plugins.Runtime
}
/// <summary>
- /// Specify the host configuration data to pass to the plugin
+ /// Configures the plugin stack to retrieve plugin-local json configuration files
+ /// from the same directory as the plugin assembly file.
/// </summary>
/// <param name="builder"></param>
- /// <param name="hostConfig">A configuration element to pass to the plugin's host config element</param>
+ /// <param name="hostConfig">An optional configuration element to pass to the plugin's host config element</param>
/// <returns>The current builder instance for chaining</returns>
- public static PluginStackBuilder WithConfigurationData(this PluginStackBuilder builder, JsonElement hostConfig)
+ public static PluginStackBuilder WithLocalJsonConfig(this PluginStackBuilder builder, in JsonElement? hostConfig)
{
_ = builder ?? throw new ArgumentNullException(nameof(builder));
- //Clone the host config into binary
- using VnMemoryStream ms = new();
- using (Utf8JsonWriter writer = new(ms))
+ LocalFilePluginConfigReader reader;
+
+ //Host config is optional
+ if (hostConfig.HasValue)
+ {
+ //Clone the host config into binary
+ using VnMemoryStream ms = new();
+ using (Utf8JsonWriter writer = new(ms))
+ {
+ hostConfig.Value.WriteTo(writer);
+ }
+
+ //Create a reader from the binary
+ reader = new LocalFilePluginConfigReader(ms.ToArray());
+ }
+ else
{
- hostConfig.WriteTo(writer);
+ //Empty json
+ byte[] emptyJson = Encoding.UTF8.GetBytes("{}");
+ reader = new LocalFilePluginConfigReader(emptyJson);
}
//Store binary
- return builder.WithConfigurationData(ms.AsSpan());
+ return builder.WithConfigurationReader(reader);
}
/// <summary>
@@ -343,5 +360,60 @@ namespace VNLib.Plugins.Runtime
});
}
}
+
+ /*
+ * Assumes plugin configuration data is stored in a json file with the same name as
+ * the plugin assembly but with a json extension.
+ *
+ * The json file is local for the specific plugin and is not shared between plugins. The host
+ * configuration is also required
+ */
+ private sealed record class LocalFilePluginConfigReader(ReadOnlyMemory<byte> HostJson) : IPluginConfigReader
+ {
+ public void ReadPluginConfigData(IPluginAssemblyLoadConfig asmConfig, Stream configData)
+ {
+ //Allow comments and trailing commas
+ JsonDocumentOptions jdo = new()
+ {
+ AllowTrailingCommas = true,
+ CommentHandling = JsonCommentHandling.Skip,
+ };
+
+ //Config file is the same name as the assembly but with a json extension
+ string pluginConfigFile = Path.ChangeExtension(asmConfig.AssemblyFile, ".json");
+
+ using JsonDocument hConfig = JsonDocument.Parse(HostJson, jdo);
+
+ //Read the plugin config file
+ if (FileOperations.FileExists(pluginConfigFile))
+ {
+ //Open file stream to read data
+ using FileStream confStream = File.OpenRead(pluginConfigFile);
+
+ //Parse the config file
+ using JsonDocument pConfig = JsonDocument.Parse(confStream, jdo);
+
+ //Merge the configs
+ using JsonDocument merged = hConfig.Merge(pConfig,"host", "plugin");
+
+ //Write the merged config to the output stream
+ using Utf8JsonWriter writer = new(configData);
+ merged.WriteTo(writer);
+ }
+ else
+ {
+ byte[] pluginConfig = Encoding.UTF8.GetBytes("{}");
+
+ using JsonDocument pConfig = JsonDocument.Parse(pluginConfig, jdo);
+
+ //Merge the configs
+ using JsonDocument merged = hConfig.Merge(pConfig,"host", "plugin");
+
+ //Write the merged config to the output stream
+ using Utf8JsonWriter writer = new(configData);
+ merged.WriteTo(writer);
+ }
+ }
+ }
}
}
diff --git a/lib/Plugins.Runtime/src/PluginStackBuilder.cs b/lib/Plugins.Runtime/src/PluginStackBuilder.cs
index d05f489..eed08e2 100644
--- a/lib/Plugins.Runtime/src/PluginStackBuilder.cs
+++ b/lib/Plugins.Runtime/src/PluginStackBuilder.cs
@@ -25,17 +25,15 @@
using System;
using System.IO;
using System.Linq;
-using System.Text;
-using System.Text.Json;
using System.Reflection;
using System.Collections.Generic;
-using VNLib.Utils.IO;
using VNLib.Utils.Logging;
using VNLib.Utils.Extensions;
namespace VNLib.Plugins.Runtime
{
+
/// <summary>
/// A construction class used to build a single plugin stack.
/// </summary>
@@ -44,10 +42,10 @@ namespace VNLib.Plugins.Runtime
private IPluginDiscoveryManager? DiscoveryManager;
private bool HotReload;
private TimeSpan ReloadDelay;
- private byte[]? HostConfigData;
+ private IPluginConfigReader? PluginConfig;
private ILogProvider? DebugLog;
- private Func<IPluginConfig, IAssemblyLoader>? Loader;
+ private Func<IPluginAssemblyLoadConfig, IAssemblyLoader>? Loader;
/// <summary>
/// Shortcut constructor for easy fluent chaining.
@@ -76,17 +74,17 @@ namespace VNLib.Plugins.Runtime
HotReload = true;
ReloadDelay = reloadDelay;
return this;
- }
+ }
/// <summary>
/// Specifies the JSON host configuration data to pass to the plugin
/// </summary>
- /// <param name="hostConfig"></param>
+ /// <param name="pluginConfig">The plugin configuration data</param>
/// <returns>The current builder instance for chaining</returns>
- public PluginStackBuilder WithConfigurationData(ReadOnlySpan<byte> hostConfig)
+ public PluginStackBuilder WithConfigurationReader(IPluginConfigReader pluginConfig)
{
//Store binary copy
- HostConfigData = hostConfig.ToArray();
+ PluginConfig = pluginConfig ?? throw new ArgumentNullException(nameof(pluginConfig));
return this;
}
@@ -96,7 +94,7 @@ namespace VNLib.Plugins.Runtime
/// </summary>
/// <param name="loaderFactory">The factory callback funtion</param>
/// <returns>The current builder instance for chaining</returns>
- public PluginStackBuilder WithLoaderFactory(Func<IPluginConfig, IAssemblyLoader> loaderFactory)
+ public PluginStackBuilder WithLoaderFactory(Func<IPluginAssemblyLoadConfig, IAssemblyLoader> loaderFactory)
{
Loader = loaderFactory;
return this;
@@ -121,9 +119,7 @@ namespace VNLib.Plugins.Runtime
public IPluginStack ConfigureStack()
{
_ = DiscoveryManager ?? throw new ArgumentException("You must specify a plugin discovery manager");
-
- //Create a default config if none was specified
- HostConfigData ??= GetEmptyConfig();
+ _ = PluginConfig ?? throw new ArgumentException("A plugin confuration reader must be specified");
//Clone the current builder state
PluginStackBuilder clone = (PluginStackBuilder)MemberwiseClone();
@@ -131,8 +127,6 @@ namespace VNLib.Plugins.Runtime
return new PluginStack(clone);
}
- private static byte[] GetEmptyConfig() => Encoding.UTF8.GetBytes("{}");
-
/*
*
@@ -172,7 +166,7 @@ namespace VNLib.Plugins.Runtime
//Create a loader for each plugin
foreach (string pluginPath in pluginPaths)
{
- PlugingAssemblyConfig pConf = new(Builder.HostConfigData)
+ PlugingAssemblyConfig pConf = new(Builder.PluginConfig!)
{
AssemblyFile = pluginPath,
WatchForReload = Builder.HotReload,
@@ -200,7 +194,7 @@ namespace VNLib.Plugins.Runtime
}
}
- internal sealed record class PluginAsmLoader(IAssemblyLoader Loader, IPluginConfig Config) : IPluginAssemblyLoader
+ internal sealed record class PluginAsmLoader(IAssemblyLoader Loader, IPluginAssemblyLoadConfig Config) : IPluginAssemblyLoader
{
///<inheritdoc/>
public void Dispose() => Loader.Dispose();
@@ -215,7 +209,7 @@ namespace VNLib.Plugins.Runtime
public void Unload() => Loader.Unload();
}
- internal sealed record class PlugingAssemblyConfig(ReadOnlyMemory<byte> HostConfig) : IPluginConfig
+ internal sealed record class PlugingAssemblyConfig(IPluginConfigReader Config) : IPluginAssemblyLoadConfig
{
///<inheritdoc/>
public bool Unloadable { get; init; }
@@ -229,54 +223,8 @@ namespace VNLib.Plugins.Runtime
///<inheritdoc/>
public TimeSpan ReloadDelay { get; init; }
- /*
- * The plugin config file is the same as the plugin assembly file,
- * but with the .json extension
- */
- private string PluginConfigFile => Path.ChangeExtension(AssemblyFile, ".json");
-
///<inheritdoc/>
- public void ReadConfigurationData(Stream outputStream)
- {
- //Allow comments and trailing commas
- JsonDocumentOptions jdo = new()
- {
- AllowTrailingCommas = true,
- CommentHandling = JsonCommentHandling.Skip,
- };
-
- using JsonDocument hConfig = JsonDocument.Parse(HostConfig, jdo);
-
- //Read the plugin config file
- if (FileOperations.FileExists(PluginConfigFile))
- {
- //Open file stream to read data
- using FileStream confStream = File.OpenRead(PluginConfigFile);
-
- //Parse the config file
- using JsonDocument pConfig = JsonDocument.Parse(confStream, jdo);
-
- //Merge the configs
- using JsonDocument merged = hConfig.Merge(pConfig,"host", "plugin");
-
- //Write the merged config to the output stream
- using Utf8JsonWriter writer = new(outputStream);
- merged.WriteTo(writer);
- }
- else
- {
- byte[] pluginConfig = Encoding.UTF8.GetBytes("{}");
-
- using JsonDocument pConfig = JsonDocument.Parse(pluginConfig, jdo);
-
- //Merge the configs
- using JsonDocument merged = hConfig.Merge(pConfig,"host", "plugin");
-
- //Write the merged config to the output stream
- using Utf8JsonWriter writer = new(outputStream);
- merged.WriteTo(writer);
- }
- }
+ public void ReadConfigurationData(Stream outputStream) => Config.ReadPluginConfigData(this, outputStream);
}
}
}
diff --git a/lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs b/lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs
index 53f63b2..c961b4e 100644
--- a/lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs
+++ b/lib/Plugins.Runtime/src/PluginUnloadExcpetion.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Runtime
@@ -35,12 +35,15 @@ namespace VNLib.Plugins.Runtime
public class PluginUnloadException : Exception
{
public PluginUnloadException()
- {}
+ { }
+
public PluginUnloadException(string message) : base(message)
- {}
+ { }
+
public PluginUnloadException(string message, Exception innerException) : base(message, innerException)
- {}
+ { }
+
protected PluginUnloadException(SerializationInfo info, StreamingContext context) : base(info, context)
- {}
+ { }
}
}
diff --git a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs
index 21d4691..a1231c9 100644
--- a/lib/Plugins.Runtime/src/RuntimePluginLoader.cs
+++ b/lib/Plugins.Runtime/src/RuntimePluginLoader.cs
@@ -46,7 +46,7 @@ namespace VNLib.Plugins.Runtime
/// <summary>
/// Gets the plugin assembly loader configuration information
/// </summary>
- public IPluginConfig Config => Loader.Config;
+ public IPluginAssemblyLoadConfig Config => Loader.Config;
/// <summary>
/// Gets the plugin lifecycle controller
diff --git a/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c b/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c
index 5daa042..8a3e65f 100644
--- a/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c
+++ b/lib/Utils.Memory/vnlib_rpmalloc/vnlib_rpmalloc.c
@@ -118,17 +118,17 @@ HEAP_METHOD_EXPORT LPVOID HEAP_METHOD_CC heapAlloc(LPVOID heap, size_t elements,
*/
GLOBAL_HEAP_INIT_CHECK
- //Allocate the block
- if (zero)
- {
- //Calloc
- return rpcalloc(elements, alignment);
- }
- else
- {
- //Alloc without zero
- return rpmalloc(size);
- }
+ //Allocate the block
+ if (zero)
+ {
+ //Calloc
+ return rpcalloc(elements, alignment);
+ }
+ else
+ {
+ //Alloc without zero
+ return rpmalloc(size);
+ }
}
else
{
@@ -161,8 +161,8 @@ HEAP_METHOD_EXPORT LPVOID HEAP_METHOD_CC heapRealloc(LPVOID heap, LPVOID block,
*/
GLOBAL_HEAP_INIT_CHECK
- //Calloc
- return rprealloc(block, size);
+ //Calloc
+ return rprealloc(block, size);
}
else
{
@@ -184,8 +184,8 @@ HEAP_METHOD_EXPORT ERRNO HEAP_METHOD_CC heapFree(LPVOID heap, LPVOID block)
GLOBAL_HEAP_INIT_CHECK
- //free block
- rpfree(block);
+ //free block
+ rpfree(block);
}
else
{
diff --git a/lib/Utils/src/Async/AsyncAccessSerializer.cs b/lib/Utils/src/Async/AsyncAccessSerializer.cs
index 0ee5a57..bdd8114 100644
--- a/lib/Utils/src/Async/AsyncAccessSerializer.cs
+++ b/lib/Utils/src/Async/AsyncAccessSerializer.cs
@@ -81,24 +81,87 @@ namespace VNLib.Utils.Async
cancellation.ThrowIfCancellationRequested();
WaitEnterToken token;
+ WaitEntry? wait;
- lock (StoreLock)
+ if (cancellation.CanBeCanceled)
+ {
+ lock (StoreLock)
+ {
+ //See if the entry already exists, otherwise get a new wait entry
+ if (!WaitTable.TryGetValue(moniker, out wait))
+ {
+ GetWaitEntry(ref wait, moniker);
+
+ //Add entry to store
+ WaitTable[moniker] = wait;
+ }
+
+ //Get waiter before leaving lock
+ wait.ScheduleWait(cancellation, out token);
+ }
+
+ //Enter wait and setup cancellation continuation
+ return EnterCancellableWait(in token, wait);
+ }
+ else
{
- //See if the entry already exists, otherwise get a new wait entry
- if (!WaitTable.TryGetValue(moniker, out WaitEntry? wait))
+ lock (StoreLock)
{
- GetWaitEntry(ref wait, moniker);
+ //See if the entry already exists, otherwise get a new wait entry
+ if (!WaitTable.TryGetValue(moniker, out wait))
+ {
+ GetWaitEntry(ref wait, moniker);
+
+ //Add entry to store
+ WaitTable[moniker] = wait;
+ }
- //Add entry to store
- WaitTable[moniker] = wait;
+ //Get waiter before leaving lock
+ wait.ScheduleWait(out token);
}
- //Get waiter before leaving lock
- wait.GetWaiter(out token);
+ //Enter the waiter without any cancellation support
+ return token.EnterWaitAsync();
}
+ }
+
+ /// <summary>
+ /// Enters a cancellable wait and sets up a continuation to release the wait entry
+ /// if a cancellation occurs
+ /// </summary>
+ /// <param name="token"></param>
+ /// <param name="entry"></param>
+ /// <returns></returns>
+ protected Task EnterCancellableWait(in WaitEnterToken token, WaitEntry entry)
+ {
+ //Inspect for a task that is already completed
+ if (token.MayYield)
+ {
+ Task awaitable = token.EnterWaitAsync();
+ _ = awaitable.ContinueWith(OnCancellableTaskContinuation, entry, TaskScheduler.Default);
+ return awaitable;
+ }
+ else
+ {
+ return token.EnterWaitAsync();
+ }
+ }
- return token.EnterWaitAsync(cancellation);
+ private void OnCancellableTaskContinuation(Task task, object? state)
+ {
+ if (!task.IsCompletedSuccessfully)
+ {
+ Debug.Assert(task.IsCanceled, "A wait task did not complete successfully but was not cancelled, this is an unexpected condition");
+
+ //store lock must be held during wait entry transition
+ lock (StoreLock)
+ {
+ //Release the wait entry
+ (state as WaitEntry)!.OnCancelled(task);
+ }
+ }
}
+
///<inheritdoc/>
public virtual void Release(TMoniker moniker)
@@ -123,7 +186,7 @@ namespace VNLib.Utils.Async
WaitEntry entry = WaitTable[moniker];
//Call release while holding store lock
- if (entry.Release(out releaser) == 0)
+ if (entry.ExitWait(out releaser) == 0)
{
//No more waiters
WaitTable.Remove(moniker);
@@ -132,6 +195,9 @@ namespace VNLib.Utils.Async
* We must release the semaphore before returning to pool,
* its safe because there are no more waiters
*/
+
+ Debug.Assert(!releaser.WillTransition, "The wait entry referrence count was 0 but a release token was issued that would cause a lock transision");
+
releaser.Release();
ReturnEntry(entry);
@@ -140,6 +206,7 @@ namespace VNLib.Utils.Async
releaser = default;
}
}
+
//Release sem outside of lock
releaser.Release();
}
@@ -177,11 +244,6 @@ namespace VNLib.Utils.Async
{
EntryPool.Push(entry);
}
- else
- {
- //Dispose entry since were not storing it
- entry.Dispose();
- }
}
/// <summary>
@@ -204,19 +266,20 @@ namespace VNLib.Utils.Async
//Cleanup the wait store
WaitTable.TrimExcess();
}
-
- //Dispose entires
- Array.ForEach(pooled, static pooled => pooled.Dispose());
}
/// <summary>
/// An entry within the wait table representing a serializer entry
/// for a given moniker
/// </summary>
- protected class WaitEntry : VnDisposeable
+ protected class WaitEntry
{
private uint _waitCount;
- private readonly SemaphoreSlim _waitHandle = new(1, 1);
+
+ /*
+ * Head of the waiting task queue
+ */
+ private TaskNode? _head;
/// <summary>
/// A stored referrnece to the moniker while the wait exists
@@ -226,12 +289,46 @@ namespace VNLib.Utils.Async
/// <summary>
/// Gets a token used to enter the lock which may block, or yield async
/// outside of a nested lock
+ /// <para>This method and release method are not thread safe</para>
+ /// </summary>
+ /// <param name="enterToken">A referrence to the wait entry token</param>
+ /// <returns>
+ /// The incremented reference count.
+ /// </returns>
+ public uint ScheduleWait(out WaitEnterToken enterToken)
+ {
+ /*
+ * Increment wait count before entering the lock
+ * A cancellation is the only way out, so cover that
+ * during the async, only if the token is cancelable
+ */
+
+ _waitCount++;
+
+ if (_waitCount != 1)
+ {
+ TaskNode waiter = InitAndEnqueueWaiter(default);
+ enterToken = new(waiter);
+ return _waitCount;
+ }
+ else
+ {
+ enterToken = default;
+ return _waitCount;
+ }
+ }
+
+ /// <summary>
+ /// Gets a token used to enter the lock which may block, or yield async
+ /// outside of a nested lock, with cancellation support
+ /// <para>This method and release method are not thread safe</para>
/// </summary>
+ /// <param name="cancellation"></param>
/// <param name="enterToken">A referrence to the wait entry token</param>
/// <returns>
/// The incremented reference count.
/// </returns>
- public uint GetWaiter(out WaitEnterToken enterToken)
+ public uint ScheduleWait(CancellationToken cancellation, out WaitEnterToken enterToken)
{
/*
* Increment wait count before entering the lock
@@ -239,25 +336,48 @@ namespace VNLib.Utils.Async
* during the async, only if the token is cancelable
*/
- enterToken = new(this);
- return Interlocked.Increment(ref _waitCount);
+ _waitCount++;
+
+ if (_waitCount != 1)
+ {
+ TaskNode waiter = InitAndEnqueueWaiter(cancellation);
+ enterToken = new(waiter);
+ return _waitCount;
+ }
+ else
+ {
+ enterToken = default;
+ return _waitCount;
+ }
}
/// <summary>
/// Prepares a release token and atomically decrements the waiter count
/// and returns the remaining number of waiters.
+ /// <para>This method and enter method are not thread safe</para>
/// </summary>
/// <param name="releaser">
/// The token that should be used to release the exclusive lock held on
/// a moniker
/// </param>
/// <returns>The number of remaining waiters</returns>
- public uint Release(out WaitReleaseToken releaser)
+ public uint ExitWait(out WaitReleaseToken releaser)
{
- releaser = new(_waitHandle);
-
//Decrement release count before leaving
- return Interlocked.Decrement(ref _waitCount);
+ --_waitCount;
+
+ TaskNode? next = _head;
+
+ if(next != null)
+ {
+ //Remove task from queue
+ _head = next.Next;
+ }
+
+ //Init releaser
+ releaser = new(next);
+
+ return _waitCount;
}
/// <summary>
@@ -270,7 +390,7 @@ namespace VNLib.Utils.Async
Moniker = moniker;
//Wait count should be 0 on calls to prepare, its a bug if not
- Debug.Assert(_waitCount == 0);
+ Debug.Assert(_waitCount == 0, "Async serializer wait count should have been reset before pooling");
}
/*
@@ -278,50 +398,70 @@ namespace VNLib.Utils.Async
* outside a nested lock
*/
- internal Task WaitAsync(CancellationToken cancellation)
- {
-
- //See if lock can be entered synchronously
- if (_waitHandle.Wait(0, CancellationToken.None))
- {
- //Lock was entered successfully without async yield
- return Task.CompletedTask;
- }
- //Lock must be entered async
+ private TaskNode InitAndEnqueueWaiter(CancellationToken cancellation)
+ {
+ TaskNode newNode = new(OnTaskCompleted, this, cancellation);
- //Check to confirm cancellation may happen
- if (cancellation.CanBeCanceled)
+ //find the tail
+ TaskNode? tail = _head;
+ if (tail == null)
{
- //Task may be cancelled, so we need to monitor the results to properly set waiting count
- Task wait = _waitHandle.WaitAsync(cancellation);
- return WaitForLockEntryWithCancellationAsync(wait);
+ _head = newNode;
}
else
{
- //Task cannot be canceled, so we dont need to monitor the results
- return _waitHandle.WaitAsync(CancellationToken.None);
+ //Find end of queue
+ while (tail.Next != null)
+ {
+ tail = tail.Next;
+ }
+ //Store new tail
+ tail.Next = newNode;
}
+ return newNode;
}
- private async Task WaitForLockEntryWithCancellationAsync(Task wait)
+ private void RemoveTask(TaskNode task)
{
- try
+ //search entire queue for task
+ TaskNode? node = _head;
+ while (node != null)
{
- await wait.ConfigureAwait(false);
- }
- catch
- {
- //Decrement wait count on error entering lock async
- _ = Interlocked.Decrement(ref _waitCount);
- throw;
+ if (node.Next == task)
+ {
+ //Remove task from queue
+ node.Next = task.Next;
+ break;
+ }
+ node = node.Next;
}
}
- ///<inheritdoc/>
- protected override void Free()
+ internal uint OnCancelled(Task instance)
+ {
+ RemoveTask((instance as TaskNode)!);
+
+ //Decrement release count before leaving
+ return --_waitCount;
+ }
+
+ private static void OnTaskCompleted(object? state)
+ { }
+
+
+ /*
+ * A linked list style task node that is used to store the
+ * next task in the queue and be awaitable as a task
+ */
+
+ private sealed class TaskNode : Task
{
- _waitHandle.Dispose();
+ public TaskNode(Action<object?> callback, object item, CancellationToken cancellation) : base(callback, item, cancellation)
+ { }
+
+ public TaskNode? Next { get; set; }
+
}
}
@@ -331,16 +471,21 @@ namespace VNLib.Utils.Async
/// </summary>
protected readonly ref struct WaitReleaseToken
{
- private readonly SemaphoreSlim? _sem;
+ private readonly Task? _nextWaiter;
- internal WaitReleaseToken(SemaphoreSlim sem) => _sem = sem;
+ /// <summary>
+ /// Indicates if releasing the lock will cause scheduling of another thread
+ /// </summary>
+ public readonly bool WillTransition => _nextWaiter != null;
+
+ internal WaitReleaseToken(Task? nextWaiter) => _nextWaiter = nextWaiter;
/// <summary>
/// Releases the exclusive lock held by the token. NOTE:
/// this method may only be called ONCE after a wait has been
/// released
/// </summary>
- public readonly void Release() => _sem?.Release();
+ public readonly void Release() => _nextWaiter?.Start();
}
/// <summary>
@@ -348,17 +493,21 @@ namespace VNLib.Utils.Async
/// </summary>
protected readonly ref struct WaitEnterToken
{
- private readonly WaitEntry _entry;
+ private readonly Task? _waiter;
+
+ /// <summary>
+ /// Indicates if a call to EnterWaitAsync will cause an awaiter to yield
+ /// </summary>
+ public bool MayYield => _waiter != null;
- internal WaitEnterToken(WaitEntry entry) => _entry = entry;
+ internal WaitEnterToken(Task wait) => _waiter = wait;
/// <summary>
/// Enters the wait for the WaitEntry. This method may not block
/// or yield (IE Return <see cref="Task.CompletedTask"/>)
/// </summary>
- /// <param name="cancellation">A token to cancel the wait for the resource</param>
/// <returns></returns>
- public Task EnterWaitAsync(CancellationToken cancellation) => _entry.WaitAsync(cancellation);
+ public Task EnterWaitAsync() => _waiter ?? Task.CompletedTask;
}
}
} \ No newline at end of file
diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs
index f4482c0..f671da0 100644
--- a/lib/Utils/src/Memory/MemoryUtil.cs
+++ b/lib/Utils/src/Memory/MemoryUtil.cs
@@ -672,6 +672,8 @@ namespace VNLib.Utils.Memory
//Get array base address
void* basePtr = (void*)arrHandle.AddrOfPinnedObject();
+ Debug.Assert(basePtr != null);
+
//Get element offset
void* indexOffet = Unsafe.Add<T>(basePtr, elementOffset);
@@ -686,10 +688,7 @@ namespace VNLib.Utils.Memory
/// <param name="pinnable">An optional <see cref="IPinnable"/> instace to wrap with the handle</param>
/// <returns>The <see cref="MemoryHandle"/> wrapper</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MemoryHandle GetMemoryHandleFromPointer(IntPtr value, GCHandle handle = default, IPinnable? pinnable = null)
- {
- return new MemoryHandle(value.ToPointer(), handle, pinnable);
- }
+ public static MemoryHandle GetMemoryHandleFromPointer(IntPtr value, GCHandle handle = default, IPinnable? pinnable = null) => new (value.ToPointer(), handle, pinnable);
/// <summary>
/// Gets a <see cref="Span{T}"/> from the supplied address
diff --git a/lib/Utils/tests/.runsettings b/lib/Utils/tests/.runsettings
index 977a261..0e7a703 100644
--- a/lib/Utils/tests/.runsettings
+++ b/lib/Utils/tests/.runsettings
@@ -2,7 +2,6 @@
<RunSettings>
<RunConfiguration>
<EnvironmentVariables>
- <VNLIB_SHARED_HEAP_FILE_PATH></VNLIB_SHARED_HEAP_FILE_PATH>
<VNLIB_SHARED_HEAP_DIAGNOSTICS>1</VNLIB_SHARED_HEAP_DIAGNOSTICS>
</EnvironmentVariables>
</RunConfiguration>
diff --git a/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs b/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs
index 5bb8b8a..7119d21 100644
--- a/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs
+++ b/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs
@@ -5,6 +5,9 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Linq;
namespace VNLib.Utils.Async.Tests
{
@@ -84,7 +87,151 @@ namespace VNLib.Utils.Async.Tests
cts.Cancel();
//Confirm the task raises cancellation
- Assert.ThrowsException<OperationCanceledException>(() => reentry.GetAwaiter().GetResult());
+ Assert.ThrowsException<TaskCanceledException>(() => reentry.GetAwaiter().GetResult());
+ }
+
+ [TestMethod()]
+ [MethodImpl(MethodImplOptions.NoOptimization)]
+ public void MultiThreadedAASTest()
+ {
+ const string DEFAULT_KEY = "default";
+
+ //Alloc serailzer base on string
+ IAsyncAccessSerializer<string> serializer = new AsyncAccessSerializer<string>(100, 100, StringComparer.Ordinal);
+
+ int maxCount = 64;
+
+ Task[] asyncArr = new int[maxCount].Select(async p =>
+ {
+ //Take a lock then random delay, then release
+ Task entry = serializer.WaitAsync(DEFAULT_KEY);
+
+ bool isCompleted = entry.IsCompleted;
+
+ Trace.WriteLineIf(isCompleted, "Wait was entered synchronously");
+
+ await Task.Delay(Random.Shared.Next(0, 10));
+
+ Trace.WriteLineIf(isCompleted != entry.IsCompleted, "Wait has transitioned to completed while waiting");
+ Trace.WriteLineIf(!entry.IsCompleted, "A call to wait will yield");
+
+ await entry;
+
+ serializer.Release(DEFAULT_KEY);
+
+ }).ToArray();
+
+ Task.WaitAll(asyncArr);
+ }
+
+ [TestMethod()]
+ [MethodImpl(MethodImplOptions.NoOptimization)]
+ public void RaceProtectionAASTest()
+ {
+ /*
+ * A very basic critical section to confirm threading consistency.
+ *
+ * Mutuating a string should be a reasonably large number of instructions due
+ * to the allocation copy, and assignment, to cause a race condition.
+ *
+ * Testing to make sure the string is updated consitently during a multi threaded
+ * process, and that a race condition occured when not using the serializer is a
+ * non-ideal test, but it is a simple test to confirm the serializer is working.
+ */
+
+ const string DEFAULT_KEY = "default";
+
+ //Alloc serailzer base on string
+ IAsyncAccessSerializer<string> serializer = new AsyncAccessSerializer<string>(100, 100, StringComparer.Ordinal);
+
+ int maxCount = 128;
+ string serialized = "";
+
+ using CancellationTokenSource cts = new(500);
+
+ Task[] asyncArr = new int[maxCount].Select(async p =>
+ {
+ //Take a lock then random delay, then release
+ await serializer.WaitAsync(DEFAULT_KEY, cts.Token);
+
+ //Increment count
+ serialized += "0";
+
+ serializer.Release(DEFAULT_KEY);
+
+ }).ToArray();
+
+ Task.WaitAll(asyncArr);
+
+ //Make sure count did not encounter any race conditions
+ Assert.AreEqual(maxCount, serialized.Length);
+ }
+
+ [TestMethod()]
+ public void SimplePerformanceComparisonTest()
+ {
+ const string DEFAULT_KEY = "default";
+
+ //Alloc serailzer base on string
+ IAsyncAccessSerializer<string> serializer = new AsyncAccessSerializer<string>(100, 100, StringComparer.Ordinal);
+
+ int maxCount = 128;
+ string test = "";
+ Stopwatch timer = new();
+
+ using CancellationTokenSource cts = new(500);
+
+ for (int i = 0; i < 10; i++)
+ {
+ test = "";
+ timer.Restart();
+
+ Task[] asyncArr = new int[maxCount].Select(async p =>
+ {
+ //Take a lock then random delay, then release
+ await serializer.WaitAsync(DEFAULT_KEY, cts.Token);
+
+ //Increment count
+ test += "0";
+
+ serializer.Release(DEFAULT_KEY);
+
+ }).ToArray();
+
+ Task.WaitAll(asyncArr);
+
+ timer.Stop();
+
+ Trace.WriteLine($"Async serialzier test completed in {timer.ElapsedTicks / 10} microseconds");
+ Assert.AreEqual(maxCount, test.Length);
+ }
+
+ using SemaphoreSlim slim = new(1,1);
+
+ for (int i = 0; i < 10; i++)
+ {
+ test = "";
+ timer.Restart();
+
+ Task[] asyncArr = new int[maxCount].Select(async p =>
+ {
+ //Take a lock then random delay, then release
+ await slim.WaitAsync(cts.Token);
+
+ //Increment count
+ test += "0";
+
+ slim.Release();
+ }).ToArray();
+
+ Task.WaitAll(asyncArr);
+
+ timer.Stop();
+
+ Trace.WriteLine($"SemaphoreSlim test completed in {timer.ElapsedTicks / 10} microseconds");
+
+ Assert.AreEqual(maxCount, test.Length);
+ }
}
}
} \ No newline at end of file
diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs
index 85d3a60..2166eea 100644
--- a/lib/Utils/tests/Memory/MemoryUtilTests.cs
+++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs
@@ -366,6 +366,7 @@ namespace VNLib.Utils.Memory.Tests
//Get stats
HeapStatistics postTest = MemoryUtil.GetSharedHeapStats();
+ Assert.IsFalse(postTest == default);
Assert.IsTrue(postTest.AllocatedBytes == preTest.AllocatedBytes + 1024);
Assert.IsTrue(postTest.AllocatedBlocks == preTest.AllocatedBlocks + 1);
diff --git a/lib/Utils/tests/Memory/NativeHeapTests.cs b/lib/Utils/tests/Memory/NativeHeapTests.cs
index d27d5fd..dde12cd 100644
--- a/lib/Utils/tests/Memory/NativeHeapTests.cs
+++ b/lib/Utils/tests/Memory/NativeHeapTests.cs
@@ -7,13 +7,13 @@ namespace VNLib.Utils.Memory.Tests
[TestClass()]
public class NativeHeapTests
{
+ const string RpMallocLibPath = "../../../../../Utils.Memory/vnlib_rpmalloc/build/Debug/vnlib_rpmalloc.dll";
+
[TestMethod()]
public void LoadHeapTest()
{
- const string TEST_HEAP_FILENAME = @"rpmalloc.dll";
-
//Try to load the global heap
- using NativeHeap heap = NativeHeap.LoadHeap(TEST_HEAP_FILENAME, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories, HeapCreation.None, 0);
+ using NativeHeap heap = NativeHeap.LoadHeap(RpMallocLibPath, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories, HeapCreation.None, 0);
Assert.IsFalse(heap.IsInvalid);
diff --git a/lib/Utils/tests/VnEncodingTests.cs b/lib/Utils/tests/VnEncodingTests.cs
index f2b5e85..a467843 100644
--- a/lib/Utils/tests/VnEncodingTests.cs
+++ b/lib/Utils/tests/VnEncodingTests.cs
@@ -26,11 +26,11 @@ using System;
using System.Linq;
using System.Text;
using System.Buffers;
+using System.Diagnostics;
using System.Buffers.Text;
using System.Security.Cryptography;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System.Diagnostics;
namespace VNLib.Utils.Tests
{