diff options
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.VNCache')
17 files changed, 88 insertions, 1954 deletions
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/AddOrUpdateBuffer.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/AddOrUpdateBuffer.cs deleted file mode 100644 index a1fe2b5..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/AddOrUpdateBuffer.cs +++ /dev/null @@ -1,97 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: AddOrUpdateBuffer.cs -* -* AddOrUpdateBuffer.cs is part of VNLib.Plugins.Extensions.VNCache -* which is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Buffers; - -using VNLib.Utils; -using VNLib.Utils.Memory; -using VNLib.Utils.Extensions; -using VNLib.Data.Caching; - -namespace VNLib.Plugins.Extensions.VNCache -{ - /// <summary> - /// Implements a buffer writer that serves to serialize object data and - /// store the object data for use by the memory cache store, and the - /// remote cache store - /// </summary> - class AddOrUpdateBuffer : VnDisposeable, IBufferWriter<byte>, IObjectData - { - private int _count; - private readonly IUnmangedHeap _heap; - private MemoryHandle<byte>? _buffer; - - public AddOrUpdateBuffer(IUnmangedHeap heap) - { - _heap = heap; - } - - public void Advance(int count) - { - //Update count - _count += count; - } - - public Memory<byte> GetMemory(int sizeHint = 0) - { - throw new NotImplementedException(); - } - - public Span<byte> GetSpan(int sizeHint = 0) - { - //Round to nearest page for new size - nint newSize = MemoryUtil.NearestPage(sizeHint + _count); - - //Alloc buffer it not yet allocated - if (_buffer == null) - { - _buffer = _heap.Alloc<byte>(newSize); - } - else - { - //check for resize if allocated - _buffer.ResizeIfSmaller(newSize); - } - - return _buffer.AsSpan(_count); - } - - public void SetData(ReadOnlySpan<byte> data) - { - throw new NotSupportedException(); - } - - public ReadOnlySpan<byte> GetData() - { - //Get stored data from within handle - return _buffer!.AsSpan(0, _count); - } - - protected override void Free() - { - _buffer?.Dispose(); - } - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs deleted file mode 100644 index cea06a3..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs +++ /dev/null @@ -1,154 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: ObjectCacheServer -* File: BucketLocalManagerFactory.cs -* -* BucketLocalManagerFactory.cs is part of ObjectCacheServer which is part of the larger -* VNLib collection of libraries and utilities. -* -* ObjectCacheServer 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. -* -* ObjectCacheServer 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.Buffers; -using System.Text.Json; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -using VNLib.Plugins; -using VNLib.Utils; -using VNLib.Utils.Memory; -using VNLib.Utils.Extensions; -using VNLib.Plugins.Extensions.Loading; - -namespace VNLib.Data.Caching.ObjectCache.Server -{ - [ConfigurationName("memory_manager", Required = false)] - internal sealed class BucketLocalManagerFactory : VnDisposeable, ICacheMemoryManagerFactory - { - private readonly LinkedList<BucketLocalManager> _managers = new (); - private readonly bool _zeroAll; - - ///<inheritdoc/> - public ICacheEntryMemoryManager CreateForBucket(uint bucketId) - { - //Init a new heap for a individual bucket - IUnmangedHeap localHeap = MemoryUtil.InitializeNewHeapForProcess(); - - BucketLocalManager manager = new (localHeap, bucketId, _zeroAll); - _managers.AddLast(manager); - - return manager; - } - - /// <summary> - /// Creates a new <see cref="BucketLocalManagerFactory"/> with the specified zero all flag - /// that is not managed by a plugin instance - /// </summary> - /// <param name="zeroAll">Forces all allocations to be zeroed before being returned to callers</param> - /// <returns></returns> - public static BucketLocalManagerFactory Create(bool zeroAll) => new (zeroAll); - - private BucketLocalManagerFactory(bool zeroAll) - { - _zeroAll = zeroAll; - } - - public BucketLocalManagerFactory(PluginBase plugin) : this(plugin, null) - { } - - public BucketLocalManagerFactory(PluginBase plugin, IConfigScope? config) - { - if (config != null) - { - //Try to get the zero all flag - if (config.TryGetValue("zero_all", out JsonElement zeroEl)) - { - _zeroAll = zeroEl.GetBoolean(); - } - } - } - - protected override void Free() - { - //Free heaps on exit - foreach (BucketLocalManager manager in _managers) - { - manager.Heap.Dispose(); - } - } - - /* - * Buckets are mutually exclusive, so we can use a single heap for each bucket - * to get a little more performance on memory operations - */ - - private sealed record class BucketLocalManager(IUnmangedHeap Heap, uint BucketId, bool Zero) : ICacheEntryMemoryManager - { - - ///<inheritdoc/> - public object AllocHandle(uint size) => Heap.Alloc<byte>(size, Zero); - - ///<inheritdoc/> - public void FreeHandle(object handle) - { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); - MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); - - //Free the handle - _handle.Dispose(); - } - - ///<inheritdoc/> - public uint GetHandleSize(object handle) - { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); - MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); - - return (uint)_handle.Length; - } - - ///<inheritdoc/> - public Span<byte> GetSpan(object handle, uint offset, uint length) - { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); - MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); - - return _handle.GetOffsetSpan(offset, checked((int)length)); - } - - ///<inheritdoc/> - public MemoryHandle PinHandle(object handle, int offset) - { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); - MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); - - //Pin the handle - return _handle.Pin(offset); - } - - ///<inheritdoc/> - public void ResizeHandle(object handle, uint newSize) - { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); - MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); - - //Resize the handle - _handle.ResizeIfSmaller(newSize); - } - } - } -} diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/ClusterNodeIndex.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/ClusterNodeIndex.cs deleted file mode 100644 index 487a4f9..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/ClusterNodeIndex.cs +++ /dev/null @@ -1,75 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: ClusterNodeIndex.cs -* -* ClusterNodeIndex.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Threading; -using System.Threading.Tasks; - -using VNLib.Utils.Logging; -using VNLib.Data.Caching.Extensions; -using VNLib.Data.Caching.Extensions.Clustering; -using VNLib.Plugins.Extensions.Loading.Events; - -namespace VNLib.Plugins.Extensions.VNCache.Clustering -{ - internal sealed class ClusterNodeIndex : IClusterNodeIndex, IIntervalScheduleable - { - private readonly CacheClientConfiguration _config; - private Task _currentUpdate; - - - public ClusterNodeIndex(CacheClientConfiguration config) - { - _config = config; - _currentUpdate = Task.CompletedTask; - } - - ///<inheritdoc/> - public CacheNodeAdvertisment? GetNextNode() - { - //Get all nodes - CacheNodeAdvertisment[] ads = _config.NodeCollection.GetAllNodes(); - //Just get a random node from the collection for now - return ads.Length > 0 ? ads.SelectRandom() : null; - } - - ///<inheritdoc/> - public Task WaitForDiscoveryAsync(CancellationToken cancellationToken) - { - return _currentUpdate.WaitAsync(cancellationToken); - } - - /// <summary> - /// Runs the discovery process and updates the current update task - /// </summary> - /// <param name="log"></param> - /// <param name="cancellationToken">A token to cancel the operation</param> - /// <returns>A task that completes when the discovery process is complete</returns> - public Task OnIntervalAsync(ILogProvider log, CancellationToken cancellationToken) - { - //Run discovery operation and update the task - _currentUpdate = _config.DiscoverNodesAsync(cancellationToken); - return Task.CompletedTask; - } - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/IClusterNodeIndex.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/IClusterNodeIndex.cs deleted file mode 100644 index ffbfa0d..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/IClusterNodeIndex.cs +++ /dev/null @@ -1,49 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: IClusterNodeIndex.cs -* -* IClusterNodeIndex.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Threading; -using System.Threading.Tasks; - -using VNLib.Data.Caching.Extensions.Clustering; - -namespace VNLib.Plugins.Extensions.VNCache.Clustering -{ - internal interface IClusterNodeIndex - { - /// <summary> - /// Gets the next available node using the configured balancing policy - /// or null if no nodes are available - /// </summary> - /// <returns>The next available node to connect to if any are available</returns> - CacheNodeAdvertisment? GetNextNode(); - - /// <summary> - /// Waits for the discovery process to complete. This is just incase a - /// connection wants to happen while a long discovery is processing. - /// </summary> - /// <param name="cancellationToken">A token to cancel the operation</param> - /// <returns>A task that resolves when the discovery process completes</returns> - Task WaitForDiscoveryAsync(CancellationToken cancellationToken); - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs index 363e1c9..befa14a 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs @@ -31,6 +31,7 @@ using VNLib.Data.Caching; namespace VNLib.Plugins.Extensions.VNCache.DataModel { + /// <summary> /// Provides cache extensions for entity caching /// </summary> @@ -54,7 +55,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// <param name="cancellation">A token to cancel the operation</param> /// <returns>A task that completes when the delete operation has compelted</returns> /// <exception cref="ArgumentNullException"></exception> - public static Task RemoveAsync<T>(this IEntityCache<T> cache, T entity, CancellationToken cancellation) where T: class, ICacheEntity + public static Task<bool> RemoveAsync<T>(this IEntityCache<T> cache, T entity, CancellationToken cancellation) where T: class, ICacheEntity { _ = entity ?? throw new ArgumentNullException(nameof(entity)); _ = cache ?? throw new ArgumentNullException(nameof(entity)); @@ -91,7 +92,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// <param name="deserializer">The entity data deserializer</param> /// <returns>The new <see cref="IEntityCache{T}"/> wrapper instance</returns> /// <exception cref="ArgumentNullException"></exception> - public static IEntityCache<T> CreateEntityCache<T>(this IGlobalCacheProvider cache, ICacheObjectSerialzer serialier, ICacheObjectDeserialzer deserializer) where T: class + public static IEntityCache<T> CreateEntityCache<T>(this IGlobalCacheProvider cache, ICacheObjectSerializer serialier, ICacheObjectDeserializer deserializer) where T: class { _ = cache ?? throw new ArgumentNullException(nameof(cache)); _ = serialier ?? throw new ArgumentNullException(nameof(serialier)); @@ -118,10 +119,10 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel private sealed class EntityCacheImpl<T> : IEntityCache<T> where T : class { private readonly IGlobalCacheProvider _cacheProvider; - private readonly ICacheObjectDeserialzer _cacheObjectDeserialzer; - private readonly ICacheObjectSerialzer _cacheObjectSerialzer; + private readonly ICacheObjectDeserializer _cacheObjectDeserialzer; + private readonly ICacheObjectSerializer _cacheObjectSerialzer; - public EntityCacheImpl(IGlobalCacheProvider cache, ICacheObjectDeserialzer deserializer, ICacheObjectSerialzer serializer) + public EntityCacheImpl(IGlobalCacheProvider cache, ICacheObjectDeserializer deserializer, ICacheObjectSerializer serializer) { _cacheProvider = cache; _cacheObjectDeserialzer = deserializer; @@ -132,7 +133,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel public Task<T?> GetAsync(string id, CancellationToken token = default) => _cacheProvider.GetAsync<T>(id, _cacheObjectDeserialzer, token); ///<inheritdoc/> - public Task RemoveAsync(string id, CancellationToken token = default) => _cacheProvider.DeleteAsync(id, token); + public Task<bool> RemoveAsync(string id, CancellationToken token = default) => _cacheProvider.DeleteAsync(id, token); ///<inheritdoc/> public Task UpsertAsync(string id, T entity, CancellationToken token = default) => _cacheProvider.AddOrUpdateAsync(id, null, entity, _cacheObjectSerialzer, token); @@ -173,7 +174,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel } ///<inheritdoc/> - public override Task DeleteAsync(string key, CancellationToken cancellation) + public override Task<bool> DeleteAsync(string key, CancellationToken cancellation) { _ = key ?? throw new ArgumentNullException(nameof(key)); //Compute the key for the id @@ -193,7 +194,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel } ///<inheritdoc/> - public override Task<T> GetAsync<T>(string key, ICacheObjectDeserialzer deserializer, CancellationToken cancellation) + public override Task<T> GetAsync<T>(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation) { _ = key ?? throw new ArgumentNullException(nameof(key)); @@ -204,7 +205,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel } ///<inheritdoc/> - public override Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation) + public override Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation) { _ = key ?? throw new ArgumentNullException(nameof(key)); @@ -216,20 +217,20 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel return cache.AddOrUpdateAsync(primary, secondary, value, serialzer, cancellation); } - + ///<inheritdoc/> - public override Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation) + public override Task GetAsync<T>(string key, ObjectDataSet<T> callback, T state, CancellationToken cancellation) { _ = key ?? throw new ArgumentNullException(nameof(key)); //Compute the key for the id string scoped = KeyGen.ComputedKey(key); - return cache.GetAsync(scoped, rawData, cancellation); + return cache.GetAsync(scoped, callback, state, cancellation); } ///<inheritdoc/> - public override Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) + public override Task AddOrUpdateAsync<T>(string key, string? newKey, ObjectDataReader<T> callback, T state, CancellationToken cancellation) { _ = key ?? throw new ArgumentNullException(nameof(key)); @@ -239,8 +240,11 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel //If newkey exists, compute the secondary key string? secondary = newKey != null ? KeyGen.ComputedKey(newKey) : null; - return cache.AddOrUpdateAsync(primary, secondary, rawData, cancellation); + return cache.AddOrUpdateAsync(primary, secondary, callback, state, cancellation); } + + ///<inheritdoc/> + public override object GetUnderlyingStore() => cache.GetUnderlyingStore(); } } diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCache.cs index e99591b..354d126 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCache.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCache.cs @@ -57,7 +57,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// <param name="id">The id of the item to remove</param> /// <param name="token">A token to cancel delete opdation</param> /// <returns>A task that completes when the item has been deleted successfully</returns> - Task RemoveAsync(string id, CancellationToken token = default); + Task<bool> RemoveAsync(string id, CancellationToken token = default); } } diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs index d949bde..6ad902d 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs @@ -50,21 +50,24 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel public abstract Task AddOrUpdateAsync<T>(string key, string? newKey, T value, CancellationToken cancellation); ///<inheritdoc/> - public abstract Task DeleteAsync(string key, CancellationToken cancellation); + public abstract Task<bool> DeleteAsync(string key, CancellationToken cancellation); ///<inheritdoc/> public abstract Task<T?> GetAsync<T>(string key, CancellationToken cancellation); ///<inheritdoc/> - public abstract Task<T?> GetAsync<T>(string key, ICacheObjectDeserialzer deserializer, CancellationToken cancellation); + public abstract Task<T?> GetAsync<T>(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation); ///<inheritdoc/> - public abstract Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation); + public abstract Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation); ///<inheritdoc/> - public abstract Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation); + public abstract Task GetAsync<T>(string key, ObjectDataSet<T> callback, T state, CancellationToken cancellation); ///<inheritdoc/> - public abstract Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation); + public abstract Task AddOrUpdateAsync<T>(string key, string? newKey, ObjectDataReader<T> callback, T state, CancellationToken cancellation); + + ///<inheritdoc/> + public abstract object GetUnderlyingStore(); } } diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs deleted file mode 100644 index a56529b..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs +++ /dev/null @@ -1,222 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: MemoryCache.cs -* -* MemoryCache.cs is part of VNLib.Plugins.Extensions.VNCache -* which is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -using VNLib.Utils; -using VNLib.Utils.Memory; -using VNLib.Utils.Logging; -using VNLib.Utils.Memory.Diagnostics; -using VNLib.Data.Caching; -using VNLib.Data.Caching.ObjectCache; -using VNLib.Plugins.Extensions.Loading; -using VNLib.Data.Caching.ObjectCache.Server; - -namespace VNLib.Plugins.Extensions.VNCache -{ - [ConfigurationName(VNCacheExtensions.CACHE_CONFIG_KEY)] - internal sealed class MemoryCache : VnDisposeable, IGlobalCacheProvider - { - const int MB_DIVISOR = 1000 * 1024; - - const string DEBUG_TEMPLATE =@"Configuring Memory-Only Cache - | ----------------------------- - | Configuration: - | Table Size: {ts} - | Bucket Size: {bs} - | Max Objects: {obj} - | Max Memory Estimations: - | 4K blocks: {4k}Mb - | 8K blocks: {8k}Mb - | 16K blocks: {16K}Mb - | ----------------------------- -"; - - private readonly ICacheObjectSerialzer _serialzer; - private readonly ICacheObjectDeserialzer _deserialzer; - private readonly IBlobCacheTable _memCache; - private readonly IUnmangedHeap _bufferHeap; - private readonly BucketLocalManagerFactory _blobCacheMemManager; - - public MemoryCache(PluginBase pbase, IConfigScope config) - :this( - config[VNCacheExtensions.MEMORY_CACHE_CONFIG_KEY].Deserialize<MemoryCacheConfig>()!, - pbase.IsDebug(), - pbase.Log - ) - { } - - public MemoryCache(MemoryCacheConfig config):this(config, false, null) - { } - - private MemoryCache(MemoryCacheConfig config, bool isDebug, ILogProvider? log) - { - //Validate config - config.Validate(); - - if (isDebug) - { - //Use the debug heap - IUnmangedHeap newHeap = MemoryUtil.InitializeNewHeapForProcess(); - - //Wrap in diag heap - _bufferHeap = new TrackedHeapWrapper(newHeap, true); - } - else - { - //Init new "private" heap to alloc buffer from - _bufferHeap = MemoryUtil.InitializeNewHeapForProcess(); - } - - _blobCacheMemManager = BucketLocalManagerFactory.Create(config.ZeroAllAllocations); - - //Setup cache table - _memCache = new BlobCacheTable(config.TableSize, config.BucketSize, _blobCacheMemManager, null); - - /* - * Default to json serialization by using the default - * serializer and JSON options - */ - - JsonCacheObjectSerializer defaultSerializer = new(); - _serialzer = defaultSerializer; - _deserialzer = defaultSerializer; - - PrintDebug(log, config); - } - - private static void PrintDebug(ILogProvider? log, MemoryCacheConfig config) - { - long maxObjects = config.BucketSize * config.TableSize; - - long size4kMb = (maxObjects * 4096)/MB_DIVISOR; - long size8kMb = (maxObjects * 8128)/MB_DIVISOR; - long size16kMb = (maxObjects * 16384)/MB_DIVISOR; - - log?.Debug(DEBUG_TEMPLATE, config.TableSize, config.BucketSize, maxObjects, size4kMb, size8kMb, size16kMb); - } - - ///<inheritdoc/> - public bool IsConnected { get; } = true; - - protected override void Free() - { - _memCache.Dispose(); - _bufferHeap.Dispose(); - _blobCacheMemManager.Dispose(); - } - - ///<inheritdoc/> - public Task AddOrUpdateAsync<T>(string key, string? newKey, T value, CancellationToken cancellation) => AddOrUpdateAsync(key, newKey, value, _serialzer, cancellation); - - ///<inheritdoc/> - public async Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation) - { - Check(); - - //Alloc serialzation buffer - using AddOrUpdateBuffer buffer = new (_bufferHeap); - - //Serialze the value - serialzer.Serialize(value, buffer); - - //Update object data - await _memCache.AddOrUpdateObjectAsync(key, newKey, static b => b.GetData(), buffer, default, cancellation); - } - - ///<inheritdoc/> - public Task DeleteAsync(string key, CancellationToken cancellation) - { - Check(); - return _memCache.DeleteObjectAsync(key, cancellation).AsTask(); - } - - ///<inheritdoc/> - public Task<T?> GetAsync<T>(string key, CancellationToken cancellation) => GetAsync<T>(key, _deserialzer, cancellation); - - ///<inheritdoc/> - public async Task<T?> GetAsync<T>(string key, ICacheObjectDeserialzer deserializer, CancellationToken cancellation) - { - Check(); - - IBlobCacheBucket bucket = _memCache.GetBucket(key); - - //Obtain lock - IBlobCache cache = await bucket.ManualWaitAsync(cancellation); - - try - { - //Try to read the value - if (cache.TryGetValue(key, out CacheEntry entry)) - { - return deserializer.Deserialze<T>(entry.GetDataSegment()); - } - - return default; - } - finally - { - bucket.Release(); - } - } - - ///<inheritdoc/> - public async Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation) - { - Check(); - - //Get the bucket from the desired key - IBlobCacheBucket bucket = _memCache.GetBucket(key); - - //Obtain lock - IBlobCache cache = await bucket.ManualWaitAsync(cancellation); - - try - { - //Try to read the value - if (cache.TryGetValue(key, out CacheEntry entry)) - { - //Set result data - rawData.SetData(entry.GetDataSegment()); - } - } - finally - { - bucket.Release(); - } - } - - ///<inheritdoc/> - public Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) - { - Check(); - - //Update object data - return _memCache.AddOrUpdateObjectAsync(key, newKey, static b => b.GetData(), rawData, default, cancellation).AsTask(); - } - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs deleted file mode 100644 index 57c2793..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs +++ /dev/null @@ -1,109 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: MemoryCacheConfig.cs -* -* MemoryCacheConfig.cs is part of VNLib.Plugins.Extensions.VNCache -* which is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Text.Json.Serialization; - -using VNLib.Plugins.Extensions.Loading; - -namespace VNLib.Plugins.Extensions.VNCache -{ - /// <summary> - /// Memorycache configuration object. Json-(de)serializable - /// </summary> - public sealed class MemoryCacheConfig : ICacheRefreshPolicy, IOnConfigValidation - { - /// <summary> - /// The number of buckets within the cache table - /// </summary> - [JsonPropertyName("buckets")] - public uint TableSize { get; set; } = 10; - - /// <summary> - /// The number of cache entries within each bucket - /// </summary> - [JsonPropertyName("bucket_size")] - public uint BucketSize { get; set; } = 5000; - - /// <summary> - /// The maxium size (in bytes) of each cache entry within any bucket - /// </summary> - [JsonPropertyName("max_object_size")] - public uint MaxBlobSize { get; set; } = 16 * 1024; - - [JsonIgnore] - public TimeSpan MaxCacheAge { get; set; } = TimeSpan.FromMinutes(1); - - /// <summary> - /// When refresh intervals are configured, The maxium cache entry age in seconds. - /// </summary> - [JsonPropertyName("max_age_sec")] - public uint MaxAgeSeconds - { - get => (uint)MaxCacheAge.TotalSeconds; - set => MaxCacheAge = TimeSpan.FromSeconds(value); - } - /* - * Default disable cache - */ - [JsonIgnore] - public TimeSpan RefreshInterval { get; set; } = TimeSpan.Zero; - - /// <summary> - /// The time (in seconds) a cache entry refresh interval will occur - /// if scheduled on a plugin - /// </summary> - [JsonPropertyName("refresh_interval_sec")] - public uint RefreshIntervalSeconds - { - get => (uint)RefreshInterval.TotalSeconds; - set => RefreshInterval = TimeSpan.FromSeconds(value); - } - - /// <summary> - /// Zeros all cache entry memory allocations before they are used - /// </summary> - [JsonPropertyName("zero_all")] - public bool ZeroAllAllocations { get; set; } - - ///<inheritdoc/> - public void Validate() - { - if(TableSize == 0) - { - throw new ArgumentException("You must specify a cache bucket table size", "buckets"); - } - - if(BucketSize == 0) - { - throw new ArgumentException("You must specify the maxium number of entires allowed in each bucket ", "bucket_size"); - } - - if(MaxBlobSize < 16) - { - throw new ArgumentException("You must configure a maximum object size", "max_object_size"); - } - } - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheOperator.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheOperator.cs deleted file mode 100644 index b5b1234..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheOperator.cs +++ /dev/null @@ -1,54 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: MemoryCacheOperator.cs -* -* MemoryCacheOperator.cs is part of VNLib.Plugins.Extensions.VNCache which is -* part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Data.Caching; -using VNLib.Utils; - -namespace VNLib.Plugins.Extensions.VNCache -{ - /// <summary> - /// A disposable memory cache operator handle. When cache use is complete, you should - /// dispose this handle. You may want to schedule it for cleanup on a <see cref="PluginBase"/> - /// </summary> - public sealed class MemoryCacheOperator : VnDisposeable - { - private readonly MemoryCache _cache; - - internal MemoryCacheOperator(MemoryCache cache) - { - _cache = cache; - } - - /// <summary> - /// The configured global cache instance - /// </summary> - public IGlobalCacheProvider Cache => _cache; - - ///<inheritdoc/> - protected override void Free() - { - _cache.Dispose(); - } - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs deleted file mode 100644 index 2ab97b8..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs +++ /dev/null @@ -1,355 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: RemoteBackedMemoryCache.cs -* -* RemoteBackedMemoryCache.cs is part of VNLib.Plugins.Extensions.VNCache -* which is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Buffers; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using System.Runtime.CompilerServices; - -using VNLib.Utils; -using VNLib.Utils.Memory; -using VNLib.Utils.Logging; -using VNLib.Utils.Extensions; -using VNLib.Data.Caching; -using VNLib.Data.Caching.ObjectCache; -using VNLib.Plugins.Extensions.Loading; -using VNLib.Plugins.Extensions.Loading.Events; -using VNLib.Data.Caching.ObjectCache.Server; - -namespace VNLib.Plugins.Extensions.VNCache -{ - - /* - * A combinaed cache object that uses the blob cache data structures - * from the ObjectCache server library to implement similar memory cache - * features. All update operations are write-through operations, and a timer - * may be scheduled to refresh memorycache against the server (eventually) - * - * Memory cache is destroyed when the connection to the cache server is - * lost or is exiting - */ - - [ConfigurationName(VNCacheExtensions.CACHE_CONFIG_KEY)] - internal sealed class RemoteBackedMemoryCache : VnCacheClient, IIntervalScheduleable - { - private readonly MemoryCacheConfig _cacheConfig; - private readonly ICacheObjectSerialzer _serialzer; - private readonly ICacheObjectDeserialzer _deserialzer; - private readonly IBlobCacheTable _memCache; - private readonly BucketLocalManagerFactory? _bucketFactory; - - public RemoteBackedMemoryCache(PluginBase plugin, IConfigScope config) : base(plugin, config) - { - //Get nested memory cache config - MemoryCacheConfig? memCacheConfig = config[VNCacheExtensions.MEMORY_CACHE_CONFIG_KEY].Deserialize<MemoryCacheConfig>(); - - _ = memCacheConfig ?? throw new ArgumentNullException(VNCacheExtensions.MEMORY_CACHE_CONFIG_KEY, "Missing required memory configuration variable"); - - memCacheConfig.Validate(); - - ICacheMemoryManagerFactory manager = plugin.GetOrCreateSingleton<BucketLocalManagerFactory>(); - - //Setup cache table - _memCache = new BlobCacheTable(memCacheConfig.TableSize, memCacheConfig.BucketSize, manager, null); - - _cacheConfig = memCacheConfig; - - /* - * Default to json serialization by using the default - * serializer and JSON options - */ - - JsonCacheObjectSerializer defaultSerializer = new(); - _serialzer = defaultSerializer; - _deserialzer = defaultSerializer; - - //Schedule cache purge - if (memCacheConfig.RefreshInterval > TimeSpan.Zero) - { - plugin.ScheduleInterval(this, memCacheConfig.RefreshInterval); - } - } - - public RemoteBackedMemoryCache(VnCacheClientConfig client, MemoryCacheConfig memCache, ILogProvider? debugLog):base(client, debugLog) - { - /* - * Create a local bucket manager factory, we must handle dispal - * however, since its not managed by a plugin - */ - _bucketFactory = BucketLocalManagerFactory.Create(memCache.ZeroAllAllocations); - - //Setup mem cache table - _memCache = new BlobCacheTable(memCache.TableSize, memCache.BucketSize, _bucketFactory, null); - - _cacheConfig = memCache; - - /* - * Default to json serialization by using the default - * serializer and JSON options - */ - - JsonCacheObjectSerializer defaultSerializer = new(); - _serialzer = defaultSerializer; - _deserialzer = defaultSerializer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CheckConnected() - { - if (!IsConnected) - { - throw new InvalidOperationException("The client is not connected to the remote cache"); - } - } - - public override async Task DoWorkAsync(ILogProvider pluginLog, CancellationToken exitToken) - { - //Cleanup - try - { - await base.DoWorkAsync(pluginLog, exitToken); - } - finally - { - _memCache.Dispose(); - _bucketFactory?.Dispose(); - } - } - - ///<inheritdoc/> - public override Task AddOrUpdateAsync<T>(string key, string? newKey, T value, CancellationToken cancellation) - => AddOrUpdateAsync(key, newKey, value, _serialzer, cancellation); - - ///<inheritdoc/> - public override Task DeleteAsync(string key, CancellationToken cancellation) - { - CheckConnected(); - - //Delete the object from - Task local = _memCache.DeleteObjectAsync(key, cancellation).AsTask(); - Task remote = Client.DeleteObjectAsync(key, cancellation); - - //task when both complete - return Task.WhenAll(local, remote); - } - - ///<inheritdoc/> - public override Task<T> GetAsync<T>(string key, CancellationToken cancellation) => GetAsync<T>(key, _deserialzer, cancellation); - - ///<inheritdoc/> - public override async Task<T> GetAsync<T>(string key, ICacheObjectDeserialzer deserializer, CancellationToken cancellation) - { - CheckConnected(); - - Type objType = typeof(T); - - IBlobCacheBucket bucket = _memCache.GetBucket(key); - - //Obtain cache handle - using (CacheBucketHandle handle = await bucket.WaitAsync(cancellation)) - { - //Try to read the value - if (handle.Cache.TryGetValue(key, out CacheEntry entry)) - { - return deserializer.Deserialze<T>(entry.GetDataSegment()); - } - } - - //Alloc buffer from client heap - using ObjectGetBuffer getBuffer = new(Client.Config.BufferHeap); - - //Get the object from the server - await Client.GetObjectAsync(key, getBuffer, cancellation); - - //See if object data was set - if (getBuffer.GetData().IsEmpty) - { - return default; - } - - //Update local cache - await _memCache.AddOrUpdateObjectAsync(key, null, static b => b.GetData(), getBuffer, DateTime.UtcNow, CancellationToken.None); - - //Deserialze the entity - return deserializer.Deserialze<T>(getBuffer.GetData()); - } - - ///<inheritdoc/> - public override async Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation) - { - CheckConnected(); - - //Alloc serialzation buffer - using AddOrUpdateBuffer buffer = new (Client.Config.BufferHeap); - - //Serialze the value - serialzer.Serialize(value, buffer); - - DateTime currentTime = DateTime.UtcNow; - - try - { - //Update remote first, and if exceptions are raised, do not update local cache - await Client.AddOrUpdateObjectAsync(key, newKey, (IObjectData)buffer, cancellation); - - //Safe to update local cache - await _memCache.AddOrUpdateObjectAsync(key, newKey, static b => b.GetData(), buffer, currentTime, CancellationToken.None); - } - catch - { - //Remove local cache if exception occurs - await _memCache.DeleteObjectAsync(key, CancellationToken.None); - throw; - } - } - - ///<inheritdoc/> - public override async Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation) - { - CheckConnected(); - - IBlobCacheBucket bucket = _memCache.GetBucket(key); - - //Obtain cache handle - using (CacheBucketHandle handle = await bucket.WaitAsync(cancellation)) - { - //Try to read the value - if (handle.Cache.TryGetValue(key, out CacheEntry entry)) - { - rawData.SetData(entry.GetDataSegment()); - return; - } - } - - //Get the object from the server - await Client.GetObjectAsync(key, rawData, cancellation); - - //See if object data was set - if (rawData.GetData().IsEmpty) - { - return; - } - - //Update local cache - await _memCache.AddOrUpdateObjectAsync(key, null, static b => b.GetData(), rawData, DateTime.UtcNow, CancellationToken.None); - } - - ///<inheritdoc/> - public override async Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) - { - CheckConnected(); - - DateTime currentTime = DateTime.UtcNow; - - try - { - //Update remote first, and if exceptions are raised, do not update local cache - await Client.AddOrUpdateObjectAsync(key, newKey, rawData, cancellation); - - //Safe to update local cache - await _memCache.AddOrUpdateObjectAsync(key, newKey, static b => b.GetData(), rawData, currentTime, CancellationToken.None); - } - catch - { - //Remove local cache if exception occurs - await _memCache.DeleteObjectAsync(key, CancellationToken.None); - throw; - } - } - - async Task IIntervalScheduleable.OnIntervalAsync(ILogProvider log, CancellationToken cancellationToken) - { - if(!IsConnected) - { - return; - } - - //Get buckets - IBlobCacheBucket[] buckets = _memCache.ToArray(); - - foreach (IBlobCacheBucket bucket in buckets) - { - //enter bucket lock - using CacheBucketHandle handle = await bucket.WaitAsync(cancellationToken); - - //Prune expired entires - PruneExpired(handle.Cache); - } - } - - private void PruneExpired(IBlobCache cache) - { - DateTime current = DateTime.UtcNow; - - //Enumerate all cache entires to determine if they have expired - string[] expired = (from ec in cache - where ec.Value.GetTime().Add(_cacheConfig.MaxCacheAge) < current - select ec.Key) - .ToArray(); - - //Remove expired entires - for(int i = 0; i < expired.Length; i++) - { - cache.Remove(expired[i]); - } - - Client.Config.DebugLog?.Debug("Cleaned {mc} expired memory cache elements", expired.Length); - } - - /* - * A buffer to store object data on a cache get - */ - private sealed class ObjectGetBuffer : VnDisposeable, IObjectData - { - private IMemoryHandle<byte>? _buffer; - private readonly IUnmangedHeap _heap; - - public ObjectGetBuffer(IUnmangedHeap heap) - { - _heap = heap; - } - - public ReadOnlySpan<byte> GetData() - { - return _buffer == null ? ReadOnlySpan<byte>.Empty : _buffer.Span; - } - - public void SetData(ReadOnlySpan<byte> data) - { - //Alloc a buffer from the supplied data - _buffer = data.IsEmpty ? null : _heap.AllocAndCopy(data); - } - - protected override void Free() - { - //Free buffer - _buffer?.Dispose(); - } - } - - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteCacheOperator.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteCacheOperator.cs deleted file mode 100644 index f40f746..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteCacheOperator.cs +++ /dev/null @@ -1,83 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: RemoteCacheOperator.cs -* -* RemoteCacheOperator.cs is part of VNLib.Plugins.Extensions.VNCache which is -* part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System; -using System.Threading; -using System.Threading.Tasks; - -using VNLib.Utils.Logging; -using VNLib.Data.Caching; -using VNLib.Plugins.Extensions.Loading; - -namespace VNLib.Plugins.Extensions.VNCache -{ - /// <summary> - /// Represents a handle to a VNCache cache client, that exposes a cancellable - /// <see cref="IAsyncBackgroundWork"/> to run inside a <see cref="PluginBase"/> - /// or standlone in your own background work handler - /// </summary> - /// <remarks> - /// The background work method must be sheduled for the cache client to be - /// connected to the backing store - /// </remarks> - public sealed class RemoteCacheOperator : IAsyncBackgroundWork - { - private readonly VnCacheClient _client; - private CancellationTokenSource? _tokenSource; - - internal RemoteCacheOperator(VnCacheClient client) - { - _client = client; - } - - /// <summary> - /// The configured global cache instance - /// </summary> - public IGlobalCacheProvider Cache => _client; - - ///<inheritdoc/> - ///<exception cref="ArgumentNullException"></exception> - public async Task DoWorkAsync(ILogProvider pluginLog, CancellationToken exitToken) - { - _ = pluginLog ?? throw new ArgumentNullException(nameof(pluginLog)); - - //Create cts linked to the exit token to allow user cancellation of the listener - using(_tokenSource = CancellationTokenSource.CreateLinkedTokenSource(exitToken)) - { - //Do work with linked source - await _client.DoWorkAsync(pluginLog, _tokenSource.Token) - .ConfigureAwait(false); - } - - //Remove cts - _tokenSource = null; - } - - /// <summary> - /// Cancels the background cache client listener - /// </summary> - public void CancelListener() => _tokenSource?.Cancel(); - - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs index 995b71a..f9835a2 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs @@ -23,12 +23,17 @@ */ using System; +using System.IO; +using System.Threading.Tasks; using System.Runtime.CompilerServices; using VNLib.Hashing; using VNLib.Utils.Memory; +using VNLib.Utils.Logging; +using VNLib.Utils.Resources; using VNLib.Utils.Extensions; using VNLib.Data.Caching; +using VNLib.Plugins.Extensions.Loading; using VNLib.Plugins.Extensions.VNCache.DataModel; namespace VNLib.Plugins.Extensions.VNCache @@ -39,10 +44,61 @@ namespace VNLib.Plugins.Extensions.VNCache /// </summary> public static class VNCacheExtensions { - internal const string CACHE_CONFIG_KEY = "vncache"; - internal const string MEMORY_CACHE_CONFIG_KEY = "memory_cache"; - internal const string MEMORY_CACHE_ONLY_KEY = "memory_only"; - + internal const string CACHE_CONFIG_KEY = "cache"; + internal const string EXTERN_CACHE_LIB_PATH = "assembly_name"; + + /// <summary> + /// Loads <see cref="IGlobalCacheProvider"/> from an external asset assembly package + /// </summary> + /// <param name="plugin"></param> + /// <param name="asmDllPath">The path to the assembly that exports the global cache provider instance</param> + /// <param name="search">The directory search option</param> + /// <returns>The loaded <see cref="IGlobalCacheProvider"/> instance</returns> + public static IGlobalCacheProvider LoadCacheLibrary(this PluginBase plugin, string asmDllPath, SearchOption search = SearchOption.AllDirectories) + => plugin.CreateServiceExternal<IGlobalCacheProvider>(asmDllPath, search, null); + + /// <summary> + /// Gets the configuration assigned global cache provider, if defined. If the configuration does not + /// define a cache provider, this method returns null. This method loads a singleton instance. + /// </summary> + /// <param name="plugin"></param> + /// <returns>The assgined global cache provider or null if undefined</returns> + public static IGlobalCacheProvider? GetDefaultGlobalCache(this PluginBase plugin) + { + if (plugin.TryGetConfig(CACHE_CONFIG_KEY) == null) + { + return null; + } + + return LoadingExtensions.GetOrCreateSingleton(plugin, SingletonCacheLoader); + } + + private static IGlobalCacheProvider SingletonCacheLoader(PluginBase plugin) + { + //Get the cache configuration + IConfigScope config = plugin.GetConfig(CACHE_CONFIG_KEY); + + string dllPath = config.GetRequiredProperty(EXTERN_CACHE_LIB_PATH, p => p.GetString()!); + + plugin.Log.Verbose("Loading external cache library: {cl}", dllPath); + + IGlobalCacheProvider _client = plugin.LoadCacheLibrary(dllPath); + + //Try to call an init method if it exists + ManagedLibrary.TryGetMethod<Action>(_client, "Init")?.Invoke(); + + //Try an async version + Func<Task>? asyncInit = ManagedLibrary.TryGetMethod<Func<Task>>(_client, "InitAsync"); + + //Schedule the async init if it exists + if (asyncInit != null) + { + _ = plugin.ObserveWork(asyncInit, 100); + } + + return _client; + } + /// <summary> /// Gets a simple scoped cache based on an entity prefix. The prefix is appended @@ -80,7 +136,6 @@ namespace VNLib.Plugins.Extensions.VNCache [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ComputeBufferSize(string id) => id.Length + Prefix.Length; - string ICacheKeyGenerator.ComputedKey(string entityId) { //Compute the required character buffer size diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VNLib.Plugins.Extensions.VNCache.csproj b/lib/VNLib.Plugins.Extensions.VNCache/src/VNLib.Plugins.Extensions.VNCache.csproj index 7ea4321..f3e4c1d 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/VNLib.Plugins.Extensions.VNCache.csproj +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/VNLib.Plugins.Extensions.VNCache.csproj @@ -14,7 +14,7 @@ <Company>Vaughn Nugent</Company> <Product>VNLib.Plugins.Extensions.VNCache</Product> <PackageId>VNLib.Plugins.Extensions.VNCache</PackageId> - <Description>An Essentials framework extension library for integrating VNCache global caching provider into applications for distributed data caching, and extensions for entity caching.</Description> + <Description>An Essentials framework extension library for integrating dynamically loaded cache providers such as VNCache or Redis. Helpers and entity caching data structures are also included</Description> <Copyright>Copyright © 2023 Vaughn Nugent</Copyright> <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/VNLib.Data.Caching</PackageProjectUrl> <RepositoryUrl>https://github.com/VnUgE/VNLib.Data.Caching/tree/master/lib/VNLib.Plugins.Extensions.VNCache</RepositoryUrl> @@ -37,13 +37,7 @@ <ItemGroup> <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Loading\src\VNLib.Plugins.Extensions.Loading.csproj" /> - <ProjectReference Include="..\..\VNLib.Data.Caching.Extensions\src\VNLib.Data.Caching.Extensions.csproj" /> - <ProjectReference Include="..\..\VNLib.Data.Caching.ObjectCache\src\VNLib.Data.Caching.ObjectCache.csproj" /> <ProjectReference Include="..\..\VNLib.Data.Caching\src\VNLib.Data.Caching.csproj" /> </ItemGroup> - <ItemGroup> - <Folder Include="ClientCache\" /> - </ItemGroup> - </Project> diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs deleted file mode 100644 index e2d0176..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs +++ /dev/null @@ -1,397 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: VnCacheClient.cs -* -* VnCacheClient.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using System.Net.Sockets; -using System.Net.WebSockets; -using System.Collections.Generic; -using System.Security.Cryptography; - -using VNLib.Hashing; -using VNLib.Hashing.IdentityUtility; -using VNLib.Utils.Memory; -using VNLib.Utils.Logging; -using VNLib.Data.Caching; -using VNLib.Data.Caching.Extensions; -using VNLib.Data.Caching.ObjectCache; -using VNLib.Net.Messaging.FBM.Client; -using VNLib.Plugins.Extensions.Loading; -using VNLib.Data.Caching.Extensions.Clustering; -using VNLib.Plugins.Extensions.Loading.Events; -using VNLib.Plugins.Extensions.VNCache.Clustering; - -namespace VNLib.Plugins.Extensions.VNCache -{ - public interface ICacheRefreshPolicy - { - TimeSpan MaxCacheAge { get; } - - TimeSpan RefreshInterval { get; } - } - - /// <summary> - /// A base class that manages - /// </summary> - [ConfigurationName(VNCacheExtensions.CACHE_CONFIG_KEY)] - internal class VnCacheClient : IGlobalCacheProvider, IAsyncBackgroundWork - { - private const string LOG_NAME = "CLIENT"; - private static readonly TimeSpan InitialDelay = TimeSpan.FromSeconds(10); - private static readonly TimeSpan NoNodeDelay = TimeSpan.FromSeconds(10); - - private readonly VnCacheClientConfig _config; - private readonly ClusterNodeIndex _index; - - /// <summary> - /// The internal client - /// </summary> - public FBMClient Client { get; } - - /// <summary> - /// Gets a value that determines if the client is currently connected to a server - /// </summary> - public bool IsConnected { get; private set; } - - public VnCacheClient(PluginBase plugin, IConfigScope config) - :this( - config.Deserialze<VnCacheClientConfig>(), - plugin.IsDebug() ? plugin.Log : null - ) - { - ILogProvider scoped = plugin.Log.CreateScope(LOG_NAME); - - //Set authenticator and error handler - Client.GetCacheConfiguration() - .WithAuthenticator(new AuthManager(plugin)) - .WithErrorHandler(new DiscoveryErrHAndler(scoped)); - - //Schedule discovery interval - plugin.ScheduleInterval(_index, _config.DiscoveryInterval); - - //Run discovery after initial delay if interval is greater than initial delay - if(_config.DiscoveryInterval > InitialDelay) - { - //Run a manual initial load - scoped.Information("Running initial discovery in {delay}", InitialDelay); - _ = plugin.ObserveWork(() => _index.OnIntervalAsync(scoped, plugin.UnloadToken), (int)InitialDelay.TotalMilliseconds); - } - } - - public VnCacheClient(VnCacheClientConfig config, ILogProvider? debugLog) - { - //Validate config - (config as IOnConfigValidation).Validate(); - - _config = config; - - //Init the client with default settings - FBMClientConfig conf = FBMDataCacheExtensions.GetDefaultConfig(MemoryUtil.Shared, config.MaxMessageSize!.Value, config.RequestTimeout, debugLog); - - Client = new(conf); - - //Add the configuration to the client - Client.GetCacheConfiguration() - .WithTls(config.UseTls) - .WithInitialPeers(config.GetInitialNodeUris()); - - //Init index - _index = new ClusterNodeIndex(Client.GetCacheConfiguration()); - } - - /* - * Background work method manages the remote cache connection - * to the cache cluster - */ - public virtual async Task DoWorkAsync(ILogProvider pluginLog, CancellationToken exitToken) - { - //Scope log - pluginLog = pluginLog.CreateScope(LOG_NAME); - - try - { - //Initial delay - pluginLog.Debug("Worker started, waiting for startup delay"); - await Task.Delay((int)InitialDelay.TotalMilliseconds + 1000, exitToken); - - while (true) - { - try - { - //Wait for a discovery to complete - await _index.WaitForDiscoveryAsync(exitToken); - } - catch(CacheDiscoveryFailureException cdfe) - { - pluginLog.Error("Failed to discover nodes, will try again\n{err}", cdfe.Message); - //Continue - } - - //Get the next node to connect to - CacheNodeAdvertisment? node = _index.GetNextNode(); - - if (node is null) - { - pluginLog.Warn("No nodes available to connect to, trying again in {delay}", NoNodeDelay); - await Task.Delay(NoNodeDelay, exitToken); - - //Run another manual discovery if the interval is greater than the delay - if (_config.DiscoveryInterval > NoNodeDelay) - { - pluginLog.Debug("Forcing a manual discovery"); - - //We dont need to await this because it is awaited at the top of the loop - _ = _index.OnIntervalAsync(pluginLog, exitToken); - } - - continue; - } - - //Ready to connect - - try - { - pluginLog.Debug("Connecting to {node}", node); - - //Connect to the node - await Client.ConnectToCacheAsync(node, exitToken); - - if (pluginLog.IsEnabled(LogLevel.Debug)) - { - pluginLog.Debug("Connected server: {s}", node); - } - else - { - pluginLog.Information("Successfully connected to cache node"); - } - - //Set connection status flag - IsConnected = true; - - //Wait for disconnect - await Client.WaitForExitAsync(exitToken); - - pluginLog.Information("Cache server disconnected"); - } - catch(TimeoutException) - { - pluginLog.Warn("Failed to establish a websocket connection to cache server within the timeout period"); - } - catch (WebSocketException wse) - { - pluginLog.Warn("Failed to establish a websocket connection to cache server {reason}", wse.Message); - pluginLog.Verbose("Stack trace: {re}", wse); - } - //SEs may be raised when the server is not available - catch (HttpRequestException he) when (he.InnerException is SocketException) - { - pluginLog.Debug("Failed to connect to random cache server because a TCP connection could not be established"); - pluginLog.Verbose("Stack trace: {re}", he.InnerException); - } - catch(HttpRequestException he) when(he.StatusCode.HasValue) - { - pluginLog.Warn("Failed to negotiate with cache server {reason}", he.Message); - pluginLog.Verbose("Stack trace: {re}", he); - await Task.Delay(1000, exitToken); - } - finally - { - IsConnected = false; - } - - //Loop again - } - } - catch (OperationCanceledException) - { - //Normal exit from listening loop - } - catch (FBMServerNegiationException fne) - { - pluginLog.Error("Failed to negotiate connection with cache server. Please check your configuration\n {reason}", fne.Message); - } - catch (Exception ex) - { - pluginLog.Error(ex, "Unhandled exception occured in background cache client listening task"); - } - finally - { - //Dispose the client on exit - Client.Dispose(); - } - pluginLog.Information("Cache client exited"); - } - - - ///<inheritdoc/> - public virtual Task AddOrUpdateAsync<T>(string key, string? newKey, T value, CancellationToken cancellation) - { - return !IsConnected - ? throw new InvalidOperationException("The underlying client is not connected to a cache node") - : Client!.AddOrUpdateObjectAsync(key, newKey, value, cancellation); - } - - ///<inheritdoc/> - public virtual Task DeleteAsync(string key, CancellationToken cancellation) - { - return !IsConnected - ? throw new InvalidOperationException("The underlying client is not connected to a cache node") - : Client!.DeleteObjectAsync(key, cancellation); - } - - ///<inheritdoc/> - public virtual Task<T?> GetAsync<T>(string key, CancellationToken cancellation) - { - return !IsConnected - ? throw new InvalidOperationException("The underlying client is not connected to a cache node") - : Client!.GetObjectAsync<T>(key, cancellation); - } - - ///<inheritdoc/> - public virtual Task<T?> GetAsync<T>(string key, ICacheObjectDeserialzer deserializer, CancellationToken cancellation) - { - return !IsConnected - ? throw new InvalidOperationException("The underlying client is not connected to a cache node") - : Client!.GetObjectAsync<T>(key, deserializer, cancellation); - } - - ///<inheritdoc/> - public virtual Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation) - { - return !IsConnected - ? throw new InvalidOperationException("The underlying client is not connected to a cache node") - : Client!.AddOrUpdateObjectAsync(key, newKey, value, serialzer, cancellation); - } - - ///<inheritdoc/> - public virtual Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation) - { - return !IsConnected - ? throw new InvalidOperationException("The underlying client is not connected to a cache node") - : Client!.GetObjectAsync(key, rawData, cancellation); - } - - ///<inheritdoc/> - public virtual Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) - { - return !IsConnected - ? throw new InvalidOperationException("The underlying client is not connected to a cache node") - : Client!.AddOrUpdateObjectAsync(key, newKey, rawData, cancellation); - } - - private sealed class AuthManager : ICacheAuthManager - { - - private IAsyncLazy<ReadOnlyJsonWebKey> _sigKey; - private IAsyncLazy<ReadOnlyJsonWebKey> _verKey; - - public AuthManager(PluginBase plugin) - { - //Lazy load keys - - //Get the signing key - _sigKey = plugin.GetSecretAsync("client_private_key").ToLazy(static r => r.GetJsonWebKey()); - - //Lazy load cache public key - _verKey = plugin.GetSecretAsync("cache_public_key").ToLazy(static r => r.GetJsonWebKey()); - } - - public async Task AwaitLazyKeyLoad() - { - await _sigKey; - await _verKey; - } - - ///<inheritdoc/> - public IReadOnlyDictionary<string, string?> GetJwtHeader() - { - //Get the signing key jwt header - return _sigKey.Value.JwtHeader; - } - - ///<inheritdoc/> - public void SignJwt(JsonWebToken jwt) - { - //Sign the jwt with signing key - jwt.SignFromJwk(_sigKey.Value); - } - - ///<inheritdoc/> - public byte[] SignMessageHash(byte[] hash, HashAlg alg) - { - //try to get the rsa alg for the signing key - using RSA? rsa = _sigKey.Value.GetRSAPrivateKey(); - if(rsa != null) - { - return rsa.SignHash(hash, alg.GetAlgName(), RSASignaturePadding.Pkcs1); - } - - //try to get the ecdsa alg for the signing key - using ECDsa? ecdsa = _sigKey.Value.GetECDsaPrivateKey(); - if(ecdsa != null) - { - return ecdsa.SignHash(hash); - } - - throw new NotSupportedException("The signing key is not a valid RSA or ECDSA key"); - } - - ///<inheritdoc/> - public bool VerifyJwt(JsonWebToken jwt, bool isPeer) - { - return jwt.VerifyFromJwk(_verKey.Value); - } - - ///<inheritdoc/> - public bool VerifyMessageHash(ReadOnlySpan<byte> hash, HashAlg alg, ReadOnlySpan<byte> signature, bool isPeer) - { - //try to get the rsa alg for the signing key - using RSA? rsa = _verKey.Value.GetRSAPublicKey(); - if (rsa != null) - { - return rsa.VerifyHash(hash, signature, alg.GetAlgName(), RSASignaturePadding.Pkcs1); - } - - //try to get the ecdsa alg for the signing key - using ECDsa? ecdsa = _verKey.Value.GetECDsaPublicKey(); - if (ecdsa != null) - { - return ecdsa.VerifyHash(hash, signature); - } - - throw new NotSupportedException("The current key is not an RSA or ECDSA key and is not supported"); - } - } - - private sealed record class DiscoveryErrHAndler(ILogProvider Logger) : ICacheDiscoveryErrorHandler - { - public void OnDiscoveryError(CacheNodeAdvertisment errorNode, Exception ex) - { - Logger.Error("Failed to discover nodes from server {s} cause:\n{err}", errorNode?.NodeId, ex); - } - } - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClientConfig.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClientConfig.cs deleted file mode 100644 index bfa9d92..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClientConfig.cs +++ /dev/null @@ -1,139 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: VnCacheClientConfig.cs -* -* VnCacheClientConfig.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Text.Json.Serialization; - -using VNLib.Plugins.Extensions.Loading; - -namespace VNLib.Plugins.Extensions.VNCache -{ - /// <summary> - /// Represents a remote VNCache client configuration - /// </summary> - public class VnCacheClientConfig : IOnConfigValidation - { - /// <summary> - /// The maximum size (in bytes) of messages sent to the - /// cache server. This value will be negotiated with the server - /// during a connection upgrade - /// </summary> - [JsonPropertyName("max_object_size")] - public int? MaxMessageSize { get; set; } - - /// <summary> - /// The broker server address - /// </summary> - [JsonPropertyName("use_tls")] - public bool UseTls { get; set; } = true; - - /// <summary> - /// The time (in seconds) to randomly delay polling the broker server - /// for available servers - /// </summary> - [JsonPropertyName("discovery_interval_Sec")] - public int? DiscoveryIntervalSeconds { get; set; } - - /// <summary> - /// The maximum time (in seconds) for FBM cache operations are allowed - /// to take before timing out. - /// </summary> - /// <remarks> - /// NOTE: You should set this value to something reasonable as FBM messages can - /// be lost and cause deadlocks if your cache implementation does not rely on - /// CancellationTokens - /// </remarks> - [JsonPropertyName("request_timeout_sec")] - public int? RequestTimeoutSeconds { get; set; } - - /// <summary> - /// Retry interval in a timespan - /// </summary> - internal TimeSpan DiscoveryInterval => TimeSpan.FromSeconds(DiscoveryIntervalSeconds!.Value); - - /// <summary> - /// FBM Request timeout - /// </summary> - internal TimeSpan RequestTimeout => TimeSpan.FromSeconds(RequestTimeoutSeconds!.Value); - - /// <summary> - /// The initial peers to connect to - /// </summary> - [JsonPropertyName("initial_nodes")] - public string[]? InitialNodes { get; set; } - - /// <summary> - /// Gets the initial nodes as a collection of URIs - /// </summary> - /// <returns>The nodes as a collection of URIs</returns> - /// <exception cref="InvalidOperationException"></exception> - public Uri[] GetInitialNodeUris() - { - _ = InitialNodes ?? throw new InvalidOperationException("Initial nodes have not been set"); - return InitialNodes.Select(static x => new Uri(x, UriKind.Absolute)).ToArray(); - } - - void IOnConfigValidation.Validate() - { - if (!MaxMessageSize.HasValue || MaxMessageSize.Value < 1) - { - throw new ArgumentException("Your maxium message size should be a reasonable value greater than 0", "max_message_size"); - } - - if (!DiscoveryIntervalSeconds.HasValue || DiscoveryIntervalSeconds.Value < 1) - { - throw new ArgumentException("You must specify a retry interval period greater than 0", "retry_interval_sec"); - } - - //Allow a 0 timeout to disable timeouts, not recommended, but allowed - if(!RequestTimeoutSeconds.HasValue || RequestTimeoutSeconds.Value < 0) - { - throw new ArgumentException("You must specify a positive integer FBM message timoeut", "request_timeout_sec"); - } - - //Validate initial nodes - if (InitialNodes == null || InitialNodes.Length == 0) - { - throw new ArgumentException("You must specify at least one initial peer", "initial_peers"); - } - - //Validate initial nodes - foreach (Uri peer in GetInitialNodeUris()) - { - if (!peer.IsAbsoluteUri) - { - throw new ArgumentException("You must specify an absolute URI for each initial node", "initial_nodes"); - } - - //Verify http connection - if(peer.Scheme != Uri.UriSchemeHttp && peer.Scheme != Uri.UriSchemeHttps) - { - throw new ArgumentException("You must specify an HTTP or HTTPS URI for each initial node", "initial_nodes"); - } - } - } - - } -}
\ No newline at end of file diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs deleted file mode 100644 index 4ad6560..0000000 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs +++ /dev/null @@ -1,188 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Extensions.VNCache -* File: VnGlobalCache.cs -* -* VnGlobalCache.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Extensions.VNCache 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.Extensions.VNCache 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.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -using VNLib.Utils.Logging; -using VNLib.Data.Caching; -using VNLib.Plugins.Extensions.Loading; - -namespace VNLib.Plugins.Extensions.VNCache -{ - - /// <summary> - /// A wrapper to simplify a shared global cache client - /// </summary> - [ConfigurationName(VNCacheExtensions.CACHE_CONFIG_KEY)] - public sealed class VnGlobalCache : IGlobalCacheProvider - { - private readonly IGlobalCacheProvider _client; - - /// <summary> - /// Initializes an emtpy client wrapper that still requires - /// configuration loading - /// </summary> - public VnGlobalCache(PluginBase pbase, IConfigScope config) - { - if (config.TryGetValue(VNCacheExtensions.MEMORY_CACHE_CONFIG_KEY, out _)) - { - //Check for memory only flag - if (config.TryGetValue(VNCacheExtensions.MEMORY_CACHE_ONLY_KEY, out JsonElement memOnly) && memOnly.GetBoolean()) - { - //Create a memory-only cache - _client = pbase.GetOrCreateSingleton<MemoryCache>(); - } - else - { - //Remote-backed memory cache - _client = pbase.GetOrCreateSingleton<RemoteBackedMemoryCache>(); - } - } - else - { - //Setup non-memory backed cache client - _client = pbase.GetOrCreateSingleton<VnCacheClient>(); - } - } - - - /// <summary> - /// Allows you to programatically create a remote-only VNCache instance - /// </summary> - /// <param name="config">The remote cache configuration, required for VNCache remote cache servers</param> - /// <param name="debugLog">An optional FBMClient debugging log provider, should be null unless debug logging is desired </param> - /// <returns>An opreator handle that can schedule the remote cache worker task</returns> - /// <exception cref="ArgumentNullException"></exception> - /// <remarks> - /// The returned <see cref="RemoteCacheOperator"/> implements the <see cref="IAsyncBackgroundWork"/> - /// interface and must be scheduled in order to maintain a connection with the remote cache store. - /// </remarks> - public static RemoteCacheOperator CreateRemoteCache(VnCacheClientConfig config, ILogProvider? debugLog = null) - { - _ = config ?? throw new ArgumentNullException(nameof(config)); - - //Init client - VnCacheClient client = new(config, debugLog); - - //Return single handle - return new(client); - } - - /// <summary> - /// Allows you to programtically create your own instance if a VNCache remote server backed - /// memory cache programatically. - /// </summary> - /// <param name="remote">The remote cache configuration, required for VNCache remote cache servers</param> - /// <param name="memory">The local memory backed configuration</param> - /// <param name="debugLog">An optional FBMClient debugging log provider, should be null unless debug logging is desired </param> - /// <returns>An opreator handle that can schedule the remote cache worker task</returns> - /// <exception cref="ArgumentNullException"></exception> - /// <remarks> - /// The returned <see cref="RemoteCacheOperator"/> implements the <see cref="IAsyncBackgroundWork"/> - /// interface and must be scheduled in order to maintain a connection with the remote cache store. The memory cache - /// resources are released when the worker task exits. - /// </remarks> - public static RemoteCacheOperator CreateRemoteBackedMemoryCache(VnCacheClientConfig remote, MemoryCacheConfig memory, ILogProvider? debugLog) - { - _ = remote ?? throw new ArgumentNullException(nameof(remote)); - _ = memory ?? throw new ArgumentNullException(nameof(memory)); - - //Init client - RemoteBackedMemoryCache client = new(remote, memory, debugLog); - - //Return single handle - return new(client); - } - - /// <summary> - /// Allows you to programatically create a memory only <see cref="IGlobalCacheProvider"/> - /// cache instance. - /// </summary> - /// <param name="config">The memory cache configuration</param> - /// <returns> - /// A <see cref="MemoryCacheOperator"/> handle that holds a ready-to use cache instance. - /// This operator must be disposed to release held resources. - /// </returns> - /// <exception cref="ArgumentNullException"></exception> - public static MemoryCacheOperator CreateMemoryCache(MemoryCacheConfig config) - { - _ = config ?? throw new ArgumentNullException(nameof(config)); - - //Init client - MemoryCache cache = new(config); - - //Return single handle - return new(cache); - } - - - ///<inheritdoc/> - public bool IsConnected => _client.IsConnected; - - ///<inheritdoc/> - public Task AddOrUpdateAsync<T>(string key, string? newKey, T value, CancellationToken cancellation) - { - return _client.AddOrUpdateAsync(key, newKey, value, cancellation); - } - - ///<inheritdoc/> - public Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation) - { - return _client.AddOrUpdateAsync(key, newKey, value, serialzer, cancellation); - } - - ///<inheritdoc/> - public Task DeleteAsync(string key, CancellationToken cancellation) - { - return _client.DeleteAsync(key, cancellation); - } - - ///<inheritdoc/> - public Task<T?> GetAsync<T>(string key, CancellationToken cancellation) - { - return _client.GetAsync<T>(key, cancellation); - } - - ///<inheritdoc/> - public Task<T?> GetAsync<T>(string key, ICacheObjectDeserialzer deserializer, CancellationToken cancellation) - { - return _client.GetAsync<T>(key, deserializer, cancellation); - } - - ///<inheritdoc/> - public Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation) - { - return _client.GetAsync(key, rawData, cancellation); - } - - ///<inheritdoc/> - public Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) - { - return _client.AddOrUpdateAsync(key, newKey, rawData, cancellation); - } - } -}
\ No newline at end of file |