From 6b87785026ca57d6f41cff87ddbd066362f3cacc Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 14 Feb 2024 14:23:53 -0500 Subject: Squashed commit of the following: commit 456ead9bc8b0f61357bae93152ad0403c4940101 Author: vnugent Date: Tue Feb 13 14:46:35 2024 -0500 fix: #1 shared cluster index on linux & latested core updates commit a481d63f964a5d5204cac2e95141f37f9a28d573 Author: vnugent Date: Tue Jan 23 15:43:50 2024 -0500 cache extension api tweaks --- .../src/BlobCache.cs | 76 +++--- .../src/BlobCacheExtensions.cs | 28 +- .../src/BlobCacheLIstener.cs | 41 ++- .../src/IMemoryCacheEntryFactory.cs | 43 --- .../src/IPersistantCacheStore.cs | 10 +- lib/VNLib.Data.Caching/src/ClientExtensions.cs | 300 ++++++++++----------- .../src/GlobalCacheExtensions.cs | 48 +++- lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs | 44 ++- .../src/DataModel/EntityCacheExtensions.cs | 21 +- .../src/DataModel/ScopedCache.cs | 4 +- .../src/VNCacheExtensions.cs | 21 +- plugins/ObjectCacheServer/src/Cache/CacheStore.cs | 4 +- plugins/ObjectCacheServer/src/ICacheStore.cs | 4 +- .../src/RedisClientCacheEntry.cs | 14 +- .../src/VNLib.Data.Caching.Providers.Redis.csproj | 2 +- .../src/BucketLocalManagerFactory.cs | 36 ++- .../src/Clustering/ClusterNodeIndex.cs | 89 +++--- .../src/FBMCacheClient.cs | 4 +- .../src/MemoryCache.cs | 4 +- .../src/RemoteBackedMemoryCache.cs | 14 +- .../src/VNCacheBase.cs | 4 +- .../src/VNCacheClient.cs | 4 +- 22 files changed, 446 insertions(+), 369 deletions(-) delete mode 100644 lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs index 5a425ec..7b2b3b1 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.ObjectCache @@ -34,10 +34,10 @@ namespace VNLib.Data.Caching.ObjectCache /// /// A general purpose binary data storage /// - public sealed class BlobCache : LRUCache, IBlobCache, IMemoryCacheEntryFactory + public sealed class BlobCache : LRUCache, IBlobCache { private bool disposedValue; - private IPersistantCacheStore? _persistance; + private readonly IPersistantCacheStore? _persistance; /// public override bool IsReadOnly { get; } @@ -62,17 +62,12 @@ namespace VNLib.Data.Caching.ObjectCache public BlobCache(uint bucketId, int maxCapacity, ICacheEntryMemoryManager manager, IPersistantCacheStore? store) :base(maxCapacity, StringComparer.Ordinal) { - if(maxCapacity < 1) - { - throw new ArgumentException("The maxium capacity of the store must be a positive integer larger than 0", nameof(maxCapacity)); - } + ArgumentOutOfRangeException.ThrowIfLessThan(maxCapacity, 1); + ArgumentNullException.ThrowIfNull(manager); BucketId = bucketId; - _persistance = store; - - MemoryManager = manager ?? throw new ArgumentNullException(nameof(manager)); - + MemoryManager = manager; MaxCapacity = maxCapacity; //Update the lookup table size @@ -88,11 +83,11 @@ namespace VNLib.Data.Caching.ObjectCache return false; } //Use the persistant cache - return _persistance.OnCacheMiss(BucketId, key, this, out value); + return _persistance.OnCacheMiss(BucketId, key, MemoryManager, out value); } /// - protected override void Evicted(ref KeyValuePair evicted) + protected override void Evicted(ref readonly KeyValuePair evicted) { try { @@ -109,6 +104,8 @@ namespace VNLib.Data.Caching.ObjectCache /// public bool TryChangeKey(string objectId, string newId, out CacheEntry entry) { + ObjectDisposedException.ThrowIf(disposedValue, this); + //Try to get the node at the current key if (LookupTable.Remove(objectId, out LinkedListNode> ? node)) { @@ -137,6 +134,8 @@ namespace VNLib.Data.Caching.ObjectCache /// public override bool Remove(string key) { + ObjectDisposedException.ThrowIf(disposedValue, this); + //Remove from persistant store also _persistance?.OnEntryDeleted(BucketId, key); @@ -161,33 +160,23 @@ namespace VNLib.Data.Caching.ObjectCache /// public override void Clear() { - //Start from first node - LinkedListNode>? node = List.First; + ObjectDisposedException.ThrowIf(disposedValue, this); - //Classic ll node itteration - while(node != null) - { - //Dispose the cache entry - node.ValueRef.Value.Dispose(); - - //Move to next node - node = node.Next; - } - - //empty all cache entires in the store - base.Clear(); + ClearInternal(); } /// public bool Remove(string objectId, out CacheEntry entry) { + ObjectDisposedException.ThrowIf(disposedValue, this); + //Try to get the stored object - if(TryGetValue(objectId, out entry)) + if (TryGetValue(objectId, out entry)) { //remove the entry and bypass the disposal bool result = base.Remove(objectId); - Debug.Assert(result == true, "The cache entry was found in the table, but failed to remove"); + Debug.Assert(result, "The cache entry was found in the table, but failed to remove"); return true; } @@ -196,6 +185,25 @@ namespace VNLib.Data.Caching.ObjectCache return false; } + private void ClearInternal() + { + //Start from first node + LinkedListNode>? node = List.First; + + //Classic ll node itteration + while (node != null) + { + //Dispose the cache entry + node.ValueRef.Value.Dispose(); + + //Move to next node + node = node.Next; + } + + //empty all cache entires in the store + base.Clear(); + } + /// void Dispose(bool disposing) { @@ -203,7 +211,7 @@ namespace VNLib.Data.Caching.ObjectCache { if (disposing) { - Clear(); + ClearInternal(); } disposedValue = true; } @@ -216,13 +224,5 @@ namespace VNLib.Data.Caching.ObjectCache Dispose(disposing: true); GC.SuppressFinalize(this); } - - - /// - CacheEntry IMemoryCacheEntryFactory.CreateEntry(ReadOnlySpan entryData) - { - //Create entry from the internal heap - return CacheEntry.Create(entryData, MemoryManager); - } } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs index ded89d2..1681256 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.ObjectCache @@ -144,14 +144,14 @@ namespace VNLib.Data.Caching.ObjectCache this IBlobCacheTable table, string objectId, string? alternateId, - ObjectDataReader bodyData, + ObjectDataGet bodyData, T state, DateTime time, CancellationToken cancellation = default) { - - _ = table ?? throw new ArgumentNullException(nameof(table)); - _ = bodyData ?? throw new ArgumentNullException(nameof(bodyData)); + ArgumentNullException.ThrowIfNull(table); + ArgumentNullException.ThrowIfNull(bodyData); + ArgumentException.ThrowIfNullOrWhiteSpace(objectId); //See if an id change is required if (string.IsNullOrWhiteSpace(alternateId)) @@ -252,11 +252,27 @@ namespace VNLib.Data.Caching.ObjectCache /// The id of the object to delete /// A token to cancel the async lock await /// A task that completes when the item has been deleted - public static async ValueTask DeleteObjectAsync(this IBlobCacheTable table, string objectId, CancellationToken cancellation = default) + public static ValueTask DeleteObjectAsync(this IBlobCacheTable table, string objectId, CancellationToken cancellation = default) { + ArgumentNullException.ThrowIfNull(table); + //Try to get the bucket that the id should belong to IBlobCacheBucket bucket = table.GetBucket(objectId); + return DeleteObjectAsync(bucket, objectId, cancellation); + } + + /// + /// Asynchronously deletes a previously stored item + /// + /// + /// The id of the object to delete + /// A token to cancel the async lock await + /// A task that completes when the item has been deleted + public static async ValueTask DeleteObjectAsync(this IBlobCacheBucket bucket, string objectId, CancellationToken cancellation = default) + { + ArgumentNullException.ThrowIfNull(bucket); + //Wait for the bucket IBlobCache cache = await bucket.ManualWaitAsync(cancellation); diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs index 5139746..972bf5e 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.ObjectCache @@ -49,44 +49,36 @@ using static VNLib.Data.Caching.Constants; namespace VNLib.Data.Caching.ObjectCache { - /// /// An for key-value object data caching servers. /// - public class BlobCacheListener : FBMListenerBase, IDisposable + /// + /// Initialzies a new + /// + /// The cache table to work from + /// The event queue to publish changes to + /// Writes error and debug logging information + /// The heap to alloc FBM buffers and cache buffers from + /// + public class BlobCacheListener(IBlobCacheTable cache, ICacheListenerEventQueue queue, ILogProvider log, IFBMMemoryManager memoryManager) + : FBMListenerBase, IDisposable { private bool disposedValue; /// - protected override ILogProvider Log { get; } + protected override ILogProvider Log { get; } = log; /// - protected override FBMListener Listener { get; } + protected override FBMListener Listener { get; } = new(memoryManager); /// /// A queue that stores update and delete events /// - public ICacheListenerEventQueue EventQueue { get; } + public ICacheListenerEventQueue EventQueue { get; } = queue ?? throw new ArgumentNullException(nameof(queue)); /// /// The Cache store to access data blobs /// - public IBlobCacheTable Cache { get; } - - /// - /// Initialzies a new - /// - /// The cache table to work from - /// The event queue to publish changes to - /// Writes error and debug logging information - /// The heap to alloc FBM buffers and cache buffers from - /// - public BlobCacheListener(IBlobCacheTable cache, ICacheListenerEventQueue queue, ILogProvider log, IFBMMemoryManager memoryManager) - { - Log = log; - Cache = cache ?? throw new ArgumentNullException(nameof(cache)); - EventQueue = queue ?? throw new ArgumentNullException(nameof(queue)); - Listener = new(memoryManager); - } + public IBlobCacheTable Cache { get; } = cache ?? throw new ArgumentNullException(nameof(cache)); /// protected override async Task ProcessAsync(FBMContext context, T? userState, CancellationToken exitToken) @@ -254,8 +246,7 @@ namespace VNLib.Data.Caching.ObjectCache { if (!disposedValue) { - Cache.Dispose(); - + Cache.Dispose(); disposedValue = true; } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs deleted file mode 100644 index 1454fc0..0000000 --- a/lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Data.Caching.ObjectCache -* File: IMemoryCacheEntryFactory.cs -* -* IMemoryCacheEntryFactory.cs is part of VNLib.Data.Caching.ObjectCache which -* is part of the larger VNLib collection of libraries and utilities. -* -* VNLib.Data.Caching.ObjectCache 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.Data.Caching.ObjectCache 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; - -namespace VNLib.Data.Caching.ObjectCache -{ - /// - /// A factory abstraction that builds structures - /// linked to internally configured memory implementations, for cache - /// promotions. - /// - public interface IMemoryCacheEntryFactory - { - /// - /// Creates and initalizes a new from the desired object data - /// - /// The non-owned memory to copy into the the new - /// The newly initalized - CacheEntry CreateEntry(ReadOnlySpan entryData); - } -} diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs index 40f39f2..3824735 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.ObjectCache @@ -54,20 +54,16 @@ namespace VNLib.Data.Caching.ObjectCache /// This method is called while the bucket lock is held. This call is maded /// during an method call. /// - /// - /// The should be used to create the - /// cache entry for the return value. Once this method returns, the caller owns the new - /// /// /// The key identifying the entry to promot - /// The cache entry factory + /// The cache table memory manager /// The id of the bucket requesting the operation /// The newly created entry when data is found /// /// A value inidcating if the entry was successfully recovered from the persistant storage and /// was successfully promoted. /// - bool OnCacheMiss(uint bucketId, string key, IMemoryCacheEntryFactory factory, out CacheEntry entry); + bool OnCacheMiss(uint bucketId, string key, ICacheEntryMemoryManager memManager, out CacheEntry entry); /// /// Removes an entry from the backing store diff --git a/lib/VNLib.Data.Caching/src/ClientExtensions.cs b/lib/VNLib.Data.Caching/src/ClientExtensions.cs index 8273486..f0eee06 100644 --- a/lib/VNLib.Data.Caching/src/ClientExtensions.cs +++ b/lib/VNLib.Data.Caching/src/ClientExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching @@ -24,7 +24,7 @@ using System; using System.Linq; -using System.Text.Json; +using System.Buffers; using System.Threading; using System.Threading.Tasks; using System.Runtime.CompilerServices; @@ -44,14 +44,14 @@ namespace VNLib.Data.Caching /// public static class ClientExtensions { + private readonly record struct AddOrUpdateState(T State, ICacheObjectSerializer Serializer); - private static readonly JsonCacheObjectSerializer DefaultSerializer = new(256); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void LogDebug(this FBMClient client, string message, params object?[] args) { client.Config.DebugLog?.Debug($"[CACHE] : {message}", args); - } + } /// /// Updates the state of the object, and optionally updates the ID of the object. The data @@ -62,31 +62,45 @@ namespace VNLib.Data.Caching /// The id of the object to update or replace /// An optional parameter to specify a new ID for the old object /// The payload data to serialize and set as the data state of the session + /// The custom serializer to used to serialze the object to binary /// A token to cancel the operation /// A task that resolves when the server responds - /// + /// + /// /// /// /// /// /// /// - public static Task AddOrUpdateObjectAsync(this FBMClient client, string objectId, string? newId, T data, CancellationToken cancellationToken = default) + public static Task AddOrUpdateObjectAsync( + this FBMClient client, + string objectId, + string? newId, + T data, + ICacheObjectSerializer serializer, + CancellationToken cancellationToken = default) { - //Use the default/json serialzer if not specified - return AddOrUpdateObjectAsync(client, objectId, newId, data, DefaultSerializer, cancellationToken); - } + ArgumentNullException.ThrowIfNull(serializer); + + //Safe to use struct, should not get promoted to heap during update + AddOrUpdateState state = new(data, serializer); + + return AddOrUpdateObjectAsync(client, objectId, newId, Serialize, state, cancellationToken); + + static void Serialize(AddOrUpdateState state, IBufferWriter finiteWriter) + => state.Serializer.Serialize(state.State, finiteWriter); + } + /// /// Updates the state of the object, and optionally updates the ID of the object. The data /// parameter is serialized, buffered, and streamed to the remote server /// - /// /// /// The id of the object to update or replace /// An optional parameter to specify a new ID for the old object - /// The payload data to serialize and set as the data state of the session - /// The custom serializer to used to serialze the object to binary + /// An that represents the data to set /// A token to cancel the operation /// A task that resolves when the server responds /// @@ -95,64 +109,11 @@ namespace VNLib.Data.Caching /// /// /// - public static async Task AddOrUpdateObjectAsync( - this FBMClient client, - string objectId, - string? newId, - T data, - ICacheObjectSerializer serializer, - CancellationToken cancellationToken = default) + public static Task AddOrUpdateObjectAsync(this FBMClient client, string objectId, string? newId, IObjectData data, CancellationToken cancellationToken = default) { - _ = client ?? throw new ArgumentNullException(nameof(client)); - _ = serializer ?? throw new ArgumentNullException(nameof(serializer)); - - client.LogDebug("Updating object {id}, newid {nid}", objectId, newId); - - //Rent a new request - FBMRequest request = client.RentRequest(); - try - { - //Set action as get/create - request.WriteHeader(HeaderCommand.Action, Actions.AddOrUpdate); - - //Set object-id header - request.WriteHeader(Constants.ObjectId, objectId); - - //if new-id set, set the new-id header - if (!string.IsNullOrWhiteSpace(newId)) - { - request.WriteHeader(Constants.NewObjectId, newId); - } - - //Serialize the message using the request buffer - serializer.Serialize(data, request.GetBodyWriter()); - - //Make request - using FBMResponse response = await client.SendAsync(request, cancellationToken); - response.ThrowIfNotSet(); - - //Get the status code - FBMMessageHeader status = response.Headers.FirstOrDefault(static a => a.Header == HeaderCommand.Status); - - //Check status code - if (status.Value.Equals(ResponseCodes.Okay, StringComparison.OrdinalIgnoreCase)) - { - return; - } - else if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.OrdinalIgnoreCase)) - { - throw new ObjectNotFoundException($"object {objectId} not found on remote server"); - } - - //Invalid status - throw new InvalidStatusException("Invalid status code recived for object upsert request", status.ToString()); - } - finally - { - //Return the request(clears data and reset) - client.ReturnRequest(request); - } + return AddOrUpdateObjectAsync(client, objectId, newId, static d => d.GetData(), data, cancellationToken); } + /// /// Updates the state of the object, and optionally updates the ID of the object. The data @@ -161,8 +122,9 @@ namespace VNLib.Data.Caching /// /// The id of the object to update or replace /// An optional parameter to specify a new ID for the old object - /// An that represents the data to set + /// A callback method that will return the desired object data /// A token to cancel the operation + /// The state to be passed to the callback /// A task that resolves when the server responds /// /// @@ -170,11 +132,31 @@ namespace VNLib.Data.Caching /// /// /// - public static Task AddOrUpdateObjectAsync(this FBMClient client, string objectId, string? newId, IObjectData data, CancellationToken cancellationToken = default) + public static Task AddOrUpdateObjectAsync( + this FBMClient client, + string objectId, + string? newId, + ObjectDataGet callback, + T state, + CancellationToken cancellationToken = default + ) { - return AddOrUpdateObjectAsync(client, objectId, newId, static d => d.GetData(), data, cancellationToken); + //Safe to use struct, should not get promoted to heap during update + ObjectDataGetState getState = new(state, callback); + + return AddOrUpdateObjectAsync(client, objectId, newId, PutData, getState, cancellationToken); + + //Function to put the data from the callback into the writer + static void PutData(ObjectDataGetState state, IBufferWriter writer) + { + //Get the data from the callback + ReadOnlySpan data = state.Getter(state.UserState); + //Write the data to the writer + writer.Write(data); + } } + /// /// Updates the state of the object, and optionally updates the ID of the object. The data /// parameter is serialized, buffered, and streamed to the remote server @@ -186,16 +168,26 @@ namespace VNLib.Data.Caching /// A token to cancel the operation /// The state to be passed to the callback /// A task that resolves when the server responds + /// + /// /// /// /// /// /// /// - public static async Task AddOrUpdateObjectAsync(this FBMClient client, string objectId, string? newId, ObjectDataReader callback, T state, CancellationToken cancellationToken = default) + public static Task AddOrUpdateObjectAsync( + this FBMClient client, + string objectId, + string? newId, + ObjectDataReader callback, + T state, + CancellationToken cancellationToken = default + ) { - _ = client ?? throw new ArgumentNullException(nameof(client)); - _ = callback ?? throw new ArgumentNullException(nameof(callback)); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(callback); + ArgumentException.ThrowIfNullOrWhiteSpace(objectId); client.LogDebug("Updating object {id}, newid {nid}", objectId, newId); @@ -215,53 +207,84 @@ namespace VNLib.Data.Caching request.WriteHeader(Constants.NewObjectId, newId); } - //Write the message body as the objet data - request.WriteBody(callback(state)); + //Write the message body as the object data + callback(state, request.GetBodyWriter()); - //Make request - using FBMResponse response = await client.SendAsync(request, cancellationToken); - response.ThrowIfNotSet(); - - //Get the status code - FBMMessageHeader status = response.Headers.FirstOrDefault(static a => a.Header == HeaderCommand.Status); + return ExecAsync(client, request, objectId, cancellationToken); + } + catch + { + //Return the request(clears data and reset) + client.ReturnRequest(request); + throw; + } - //Check status code - if (status.Value.Equals(ResponseCodes.Okay, StringComparison.OrdinalIgnoreCase)) + static async Task ExecAsync(FBMClient client, FBMRequest request, string objectId, CancellationToken cancellationToken) + { + try { - return; + //Make request + using FBMResponse response = await client.SendAsync(request, cancellationToken); + response.ThrowIfNotSet(); + + //Get the status code + FBMMessageHeader status = response.Headers.FirstOrDefault(static a => a.Header == HeaderCommand.Status); + + //Check status code + if (status.ValueEquals(ResponseCodes.Okay, StringComparison.OrdinalIgnoreCase)) + { + return; + } + else if (status.ValueEquals(ResponseCodes.NotFound, StringComparison.OrdinalIgnoreCase)) + { + throw new ObjectNotFoundException($"object {objectId} not found on remote server"); + } + + //Invalid status + throw new InvalidStatusException("Invalid status code recived for object upsert request", status.ToString()); } - else if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.OrdinalIgnoreCase)) + finally { - throw new ObjectNotFoundException($"object {objectId} not found on remote server"); + //Return the request(clears data and reset) + client.ReturnRequest(request); } - - //Invalid status - throw new InvalidStatusException("Invalid status code recived for object upsert request", status.ToString()); - } - finally - { - //Return the request(clears data and reset) - client.ReturnRequest(request); } } + /// - /// Gets an object from the server if it exists, and uses the default serialzer to - /// recover the object + /// Gets an object from the server if it exists /// /// /// /// The id of the object to get /// A token to cancel the operation + /// A callback function that computes an object result from binary data + /// A user-state parameter to be passed back to the callback function /// A task that completes to return the results of the response payload - /// - /// + /// + /// /// /// /// - public static Task GetObjectAsync(this FBMClient client, string objectId, CancellationToken cancellationToken = default) + public static async Task GetObjectAsync( + this FBMClient client, + string objectId, + GetObjectFromData getter, + TState state, + CancellationToken cancellationToken = default + ) { - return GetObjectAsync(client, objectId, DefaultSerializer, cancellationToken); + ArgumentNullException.ThrowIfNull(getter); + + //Get state will store the object result if successfull get operation + GetObjectState st = new(state, getter); + + //Get the object, if successfull, compute the result + bool success = await GetObjectAsync(client, objectId, static (s, d) => s.ComputeResult(d), st, cancellationToken); + + //If the get operation failed, return a default value + return success ? st.Result : default; } /// @@ -273,51 +296,17 @@ namespace VNLib.Data.Caching /// A token to cancel the operation /// The custom data deserialzer used to deserialze the binary cache result /// A task that completes to return the results of the response payload + /// + /// /// /// /// - public static async Task GetObjectAsync(this FBMClient client, string objectId, ICacheObjectDeserializer deserialzer, CancellationToken cancellationToken = default) + public static Task GetObjectAsync(this FBMClient client, string objectId, ICacheObjectDeserializer deserialzer, CancellationToken cancellationToken = default) { - _ = client ?? throw new ArgumentNullException(nameof(client)); - _ = deserialzer ?? throw new ArgumentNullException(nameof(deserialzer)); - - client.LogDebug("Getting object {id}", objectId); - - //Rent a new request - FBMRequest request = client.RentRequest(); - try - { - //Set action as get/create - request.WriteHeader(HeaderCommand.Action, Actions.Get); - - //Set object id header - request.WriteHeader(Constants.ObjectId, objectId); - - //Make request - using FBMResponse response = await client.SendAsync(request, cancellationToken); - response.ThrowIfNotSet(); - - //Get the status code - FBMMessageHeader status = response.Headers.FirstOrDefault(static a => a.Header == HeaderCommand.Status); - - //Check ok status code, then its safe to deserialize - if (status.Value.Equals(ResponseCodes.Okay, StringComparison.Ordinal)) - { - return deserialzer.Deserialize(response.ResponseBody); - } - - //Object may not exist on the server yet - if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.Ordinal)) - { - return default; - } - - throw new InvalidStatusException("Invalid status code recived for object get request", status.ToString()); - } - finally - { - client.ReturnRequest(request); - } + ArgumentNullException.ThrowIfNull(deserialzer); + + //Use the deserialzer to deserialize the data as a state parameter + return GetObjectAsync(client, objectId, static (s, d) => s.Deserialize(d), deserialzer, cancellationToken); } /// @@ -330,6 +319,8 @@ namespace VNLib.Data.Caching /// An object data instance used to store the found object data /// A token to cancel the operation /// A task that completes to return the results of the response payload + /// + /// /// /// /// @@ -349,13 +340,16 @@ namespace VNLib.Data.Caching /// The state parameter to pass to the callback method /// A token to cancel the operation /// When complete, true if the object was found, false if not found, and an exception otherwise + /// + /// /// /// /// public static async Task GetObjectAsync(this FBMClient client, string objectId, ObjectDataSet setter, T state, CancellationToken cancellationToken = default) { - _ = client ?? throw new ArgumentNullException(nameof(client)); - _ = setter ?? throw new ArgumentNullException(nameof(setter)); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(setter); + ArgumentException.ThrowIfNullOrWhiteSpace(objectId); client.LogDebug("Getting object {id}", objectId); @@ -405,13 +399,16 @@ namespace VNLib.Data.Caching /// The id of the object to update or replace /// A token to cancel the operation /// A task that resolves when the operation has completed + /// + /// /// /// /// /// public static async Task DeleteObjectAsync(this FBMClient client, string objectId, CancellationToken cancellationToken = default) { - _ = client ?? throw new ArgumentNullException(nameof(client)); + ArgumentNullException.ThrowIfNull(client); + ArgumentException.ThrowIfNullOrWhiteSpace(objectId); client.LogDebug("Deleting object {id}", objectId); @@ -431,11 +428,11 @@ namespace VNLib.Data.Caching //Get the status code FBMMessageHeader status = response.Headers.FirstOrDefault(static a => a.Header == HeaderCommand.Status); - if (status.Value.Equals(ResponseCodes.Okay, StringComparison.Ordinal)) + if (status.ValueEquals(ResponseCodes.Okay, StringComparison.Ordinal)) { return true; } - else if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.OrdinalIgnoreCase)) + else if (status.ValueEquals(ResponseCodes.NotFound, StringComparison.OrdinalIgnoreCase)) { return false; } @@ -459,7 +456,8 @@ namespace VNLib.Data.Caching /// public static async Task WaitForChangeAsync(this FBMClient client, WaitForChangeResult change, CancellationToken cancellationToken = default) { - _ = change ?? throw new ArgumentNullException(nameof(change)); + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(change); //Rent a new request FBMRequest request = client.RentRequest(); @@ -473,9 +471,9 @@ namespace VNLib.Data.Caching response.ThrowIfNotSet(); - change.Status = response.Headers.FirstOrDefault(static a => a.Header == HeaderCommand.Status).Value.ToString(); - change.CurrentId = response.Headers.SingleOrDefault(static v => v.Header == Constants.ObjectId).Value.ToString(); - change.NewId = response.Headers.SingleOrDefault(static v => v.Header == Constants.NewObjectId).Value.ToString(); + change.Status = response.Headers.FirstOrDefault(static a => a.Header == HeaderCommand.Status).GetValueString(); + change.CurrentId = response.Headers.SingleOrDefault(static v => v.Header == Constants.ObjectId).GetValueString(); + change.NewId = response.Headers.SingleOrDefault(static v => v.Header == Constants.NewObjectId).GetValueString(); } finally { diff --git a/lib/VNLib.Data.Caching/src/GlobalCacheExtensions.cs b/lib/VNLib.Data.Caching/src/GlobalCacheExtensions.cs index 586df73..203219c 100644 --- a/lib/VNLib.Data.Caching/src/GlobalCacheExtensions.cs +++ b/lib/VNLib.Data.Caching/src/GlobalCacheExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching @@ -26,7 +26,8 @@ using System; using System.Threading; using System.Threading.Tasks; -#pragma warning disable CA1062 // Validate arguments of public methods +using VNLib.Net.Messaging.FBM; +using VNLib.Data.Caching.Exceptions; namespace VNLib.Data.Caching { @@ -46,6 +47,7 @@ namespace VNLib.Data.Caching /// A task that complets when the object data has been written to the data buffer public static Task GetAsync(this IGlobalCacheProvider cache, string key, IObjectData rawData, CancellationToken cancellation) { + ArgumentNullException.ThrowIfNull(cache); return cache.GetAsync(key, static (cd, data) => cd.SetData(data), rawData, cancellation); } @@ -61,6 +63,7 @@ namespace VNLib.Data.Caching /// A task that completes when the update operation has compelted public static Task AddOrUpdateAsync(this IGlobalCacheProvider cache, string key, string? newKey, IObjectData rawData, CancellationToken cancellation) { + ArgumentNullException.ThrowIfNull(cache); return cache.AddOrUpdateAsync(key, newKey, static cd => cd.GetData(), rawData, cancellation); } @@ -76,9 +79,48 @@ namespace VNLib.Data.Caching /// A task that completes when the update operation has compelted public static Task AddOrUpdateAsync(this IGlobalCacheProvider cache, string key, string? newKey, ReadOnlyMemory rawData, CancellationToken cancellation) { + ArgumentNullException.ThrowIfNull(cache); return cache.AddOrUpdateAsync(key, newKey, static cd => cd.Span, rawData, cancellation); } + /// + /// Gets an object from the server if it exists + /// + /// + /// + /// + /// The id of the object to get + /// A token to cancel the operation + /// A callback function that computes an object result from binary data + /// A user-state parameter to be passed back to the callback function + /// A task that completes to return the results of the response payload + /// + /// + /// + /// + /// + public static async Task GetAsync( + this IGlobalCacheProvider cache, + string objectId, + GetObjectFromData getter, + TState state, + CancellationToken cancellationToken = default + ) + { + ArgumentNullException.ThrowIfNull(cache); + ArgumentNullException.ThrowIfNull(getter); + + //Get state will store the object result if successfull get operation + GetObjectState st = new(state, getter); + + //Get the object, if successfull, compute the result + await cache.GetAsync(objectId, static (s, d) => s.ComputeResult(d), st, cancellationToken); + + //If the get operation failed, return a default value + return st.Result; + } + + /// /// Asynchronously gets a value from the backing cache store /// @@ -89,6 +131,7 @@ namespace VNLib.Data.Caching /// The value if found, or null if it does not exist in the store public static Task GetAsync(this IGlobalCacheProvider cache, string key, CancellationToken cancellation) { + ArgumentNullException.ThrowIfNull(cache); return cache.GetAsync(key, cache.DefaultDeserializer, cancellation); } @@ -104,6 +147,7 @@ namespace VNLib.Data.Caching /// A task that completes when the update operation has compelted public static Task AddOrUpdateAsync(this IGlobalCacheProvider cache, string key, string? newKey, T value, CancellationToken cancellation) { + ArgumentNullException.ThrowIfNull(cache); return cache.AddOrUpdateAsync(key, newKey, value, cache.DefaultSerializer, cancellation); } } diff --git a/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs b/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs index e04c9e4..1545b99 100644 --- a/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs +++ b/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching @@ -23,6 +23,7 @@ */ using System; +using System.Buffers; using System.Threading; using System.Threading.Tasks; @@ -39,12 +40,47 @@ namespace VNLib.Data.Caching public delegate void ObjectDataSet(T state, ReadOnlySpan objectData); /// - /// A delegate method that will get the raw objet data from a state object + /// A delegate method that will get the raw object data from a state object /// /// /// The state object passed to the caller /// The raw object data to store in cache - public delegate ReadOnlySpan ObjectDataReader(T state); + public delegate ReadOnlySpan ObjectDataGet(T state); + + /// + /// A delegate method that will write the raw object data to the supplied + /// data buffer + /// + /// + /// The state object passed to the caller + /// The finite sized buffer writer use to write object data to + public delegate void ObjectDataReader(T state, IBufferWriter finiteWriter); + + /// + /// A delegate method that will get an object from the raw object data + /// + /// + /// + /// Optional user-state data + /// The object data to compute the object result from + /// The resultant object + public delegate TObject GetObjectFromData(TState state, ReadOnlySpan data); + + /// + /// Internal structure used to store a callback and state for the + /// a data read/get operation on a cache object + /// + /// + /// The user-state object to pass + /// The data get callback function + internal readonly record struct ObjectDataGetState(T UserState, ObjectDataGet Getter); + + internal sealed class GetObjectState(TState State, GetObjectFromData Getter) + { + public T? Result; + + public void ComputeResult(ReadOnlySpan data) => Result = Getter(State, data); + } /// /// A global cache provider interface @@ -123,6 +159,6 @@ namespace VNLib.Data.Caching /// The callback state parameter /// A token to cancel the async operation /// A task that completes when the update operation has compelted - Task AddOrUpdateAsync(string key, string? newKey, ObjectDataReader callback, T state, CancellationToken cancellation); + Task AddOrUpdateAsync(string key, string? newKey, ObjectDataGet callback, T state, CancellationToken cancellation); } } \ 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 562c220..6b39580 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Extensions.VNCache @@ -164,12 +164,13 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// The factory callback function to produce a value when a cache miss occurs /// A token to cancel the operation /// A task that completes by returning the entity + /// /// public static async Task GetOrLoadAsync(this IEntityCache cache, string id, Func> factory, CancellationToken cancellation = default) where T : class { - _ = cache ?? throw new ArgumentNullException(nameof(cache)); - _ = id ?? throw new ArgumentNullException(nameof(id)); - _ = factory ?? throw new ArgumentNullException(nameof(factory)); + ArgumentNullException.ThrowIfNull(cache); + ArgumentNullException.ThrowIfNull(factory); + ArgumentException.ThrowIfNullOrWhiteSpace(id); //try to load the value from cache T? record = await cache.GetAsync(id, cancellation); @@ -241,7 +242,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// public override Task DeleteAsync(string key, CancellationToken cancellation) { - _ = key ?? throw new ArgumentNullException(nameof(key)); + ArgumentException.ThrowIfNullOrWhiteSpace(key); //Compute the key for the id string scoped = KeyGen.ComputedKey(key); return Cache.DeleteAsync(scoped, cancellation); @@ -250,7 +251,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// public override Task GetAsync(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation) { - _ = key ?? throw new ArgumentNullException(nameof(key)); + ArgumentException.ThrowIfNullOrWhiteSpace(key); //Compute the key for the id string scoped = KeyGen.ComputedKey(key); @@ -261,7 +262,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// public override Task AddOrUpdateAsync(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation) { - _ = key ?? throw new ArgumentNullException(nameof(key)); + ArgumentException.ThrowIfNullOrWhiteSpace(key); //Compute primary key from id string primary = KeyGen.ComputedKey(key); @@ -275,7 +276,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// public override Task GetAsync(string key, ObjectDataSet callback, T state, CancellationToken cancellation) { - _ = key ?? throw new ArgumentNullException(nameof(key)); + ArgumentException.ThrowIfNullOrWhiteSpace(key); //Compute the key for the id string scoped = KeyGen.ComputedKey(key); @@ -284,9 +285,9 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel } /// - public override Task AddOrUpdateAsync(string key, string? newKey, ObjectDataReader callback, T state, CancellationToken cancellation) + public override Task AddOrUpdateAsync(string key, string? newKey, ObjectDataGet callback, T state, CancellationToken cancellation) { - _ = key ?? throw new ArgumentNullException(nameof(key)); + ArgumentException.ThrowIfNullOrWhiteSpace(key); //Compute primary key from id string primary = KeyGen.ComputedKey(key); diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs index 545e194..5107dea 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Extensions.VNCache @@ -65,7 +65,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel public abstract Task GetAsync(string key, ObjectDataSet callback, T state, CancellationToken cancellation); /// - public abstract Task AddOrUpdateAsync(string key, string? newKey, ObjectDataReader callback, T state, CancellationToken cancellation); + public abstract Task AddOrUpdateAsync(string key, string? newKey, ObjectDataGet callback, T state, CancellationToken cancellation); /// public abstract object GetUnderlyingStore(); diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs index d191ab1..c434e22 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Extensions.VNCache @@ -112,26 +112,19 @@ namespace VNLib.Plugins.Extensions.VNCache /// public static ScopedCache GetPrefixedCache(this IGlobalCacheProvider cache, string prefix, HashAlg digest = HashAlg.SHA1, HashEncodingMode encoding = HashEncodingMode.Base64) { - _ = cache ?? throw new ArgumentNullException(nameof(cache)); - _ = prefix ?? throw new ArgumentNullException(nameof(prefix)); + ArgumentNullException.ThrowIfNull(cache); + ArgumentException.ThrowIfNullOrEmpty(prefix); //Create simple cache key generator SimpleCacheKeyImpl keyProv = new(prefix, digest, encoding); //Create the scoped cache from the simple provider return cache.GetScopedCache(keyProv); } - private sealed class SimpleCacheKeyImpl : ICacheKeyGenerator + private sealed class SimpleCacheKeyImpl(string prefix, HashAlg digest, HashEncodingMode encoding) : ICacheKeyGenerator { - private readonly string Prefix; - private readonly HashAlg Digest; - private readonly HashEncodingMode Encoding; - - public SimpleCacheKeyImpl(string prefix, HashAlg digest, HashEncodingMode encoding) - { - Prefix = prefix; - Digest = digest; - Encoding = encoding; - } + private readonly string Prefix = prefix; + private readonly HashAlg Digest = digest; + private readonly HashEncodingMode Encoding = encoding; [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ComputeBufferSize(string id) => id.Length + Prefix.Length; diff --git a/plugins/ObjectCacheServer/src/Cache/CacheStore.cs b/plugins/ObjectCacheServer/src/Cache/CacheStore.cs index 02ed9b1..75abe37 100644 --- a/plugins/ObjectCacheServer/src/Cache/CacheStore.cs +++ b/plugins/ObjectCacheServer/src/Cache/CacheStore.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: ObjectCacheServer @@ -54,7 +54,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Cache } /// - ValueTask ICacheStore.AddOrUpdateBlobAsync(string objectId, string? alternateId, ObjectDataReader bodyData, T state, CancellationToken token) + ValueTask ICacheStore.AddOrUpdateBlobAsync(string objectId, string? alternateId, ObjectDataGet bodyData, T state, CancellationToken token) { return Listener.Cache.AddOrUpdateObjectAsync(objectId, alternateId, bodyData, state, default, token); } diff --git a/plugins/ObjectCacheServer/src/ICacheStore.cs b/plugins/ObjectCacheServer/src/ICacheStore.cs index a638169..dcc9b6e 100644 --- a/plugins/ObjectCacheServer/src/ICacheStore.cs +++ b/plugins/ObjectCacheServer/src/ICacheStore.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: ObjectCacheServer @@ -38,7 +38,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server /// The state parameter to pass to the data callback /// A token to cancel the async operation /// A value task that represents the async operation - ValueTask AddOrUpdateBlobAsync(string objectId, string? alternateId, ObjectDataReader bodyData, T state, CancellationToken token = default); + ValueTask AddOrUpdateBlobAsync(string objectId, string? alternateId, ObjectDataGet bodyData, T state, CancellationToken token = default); /// /// Clears all items from the store diff --git a/plugins/VNLib.Data.Caching.Providers.Redis/src/RedisClientCacheEntry.cs b/plugins/VNLib.Data.Caching.Providers.Redis/src/RedisClientCacheEntry.cs index f80c750..360be58 100644 --- a/plugins/VNLib.Data.Caching.Providers.Redis/src/RedisClientCacheEntry.cs +++ b/plugins/VNLib.Data.Caching.Providers.Redis/src/RedisClientCacheEntry.cs @@ -211,8 +211,8 @@ namespace VNLib.Data.Caching.Providers.Redis /// public async Task AddOrUpdateAsync(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation) { - _ = key ?? throw new ArgumentNullException(nameof(key)); - _ = serialzer ?? throw new ArgumentNullException(nameof(serialzer)); + ArgumentException.ThrowIfNullOrWhiteSpace(key); + ArgumentNullException.ThrowIfNull(serialzer); //Alloc update buffer using AddOrUpdateBuffer buffer = new(_defaultHeap, InitialWriterBufferSize, false); @@ -231,7 +231,7 @@ namespace VNLib.Data.Caching.Providers.Redis } /// - public async Task AddOrUpdateAsync(string key, string? newKey, ObjectDataReader callback, T state, CancellationToken cancellation) + public async Task AddOrUpdateAsync(string key, string? newKey, ObjectDataGet callback, T state, CancellationToken cancellation) { /* * Because the redis database only allows ReadonlyMemory when @@ -253,7 +253,7 @@ namespace VNLib.Data.Caching.Providers.Redis await _database.KeyRenameAsync(key, newKey); } - static IMemoryOwner AllocAndCopy(ObjectDataReader callback, T state, IUnmangedHeap heap, ref int length) + static IMemoryOwner AllocAndCopy(ObjectDataGet callback, T state, IUnmangedHeap heap, ref int length) { //Get the buffer from the callback ReadOnlySpan data = callback(state); @@ -276,7 +276,8 @@ namespace VNLib.Data.Caching.Providers.Redis /// public async Task GetAsync(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation) { - _ = deserializer ?? throw new ArgumentNullException(nameof(deserializer)); + ArgumentException.ThrowIfNullOrWhiteSpace(key); + ArgumentNullException.ThrowIfNull(deserializer); //Try to get the value from the cache RedisValue value = await _database.StringGetAsync(key); @@ -293,7 +294,8 @@ namespace VNLib.Data.Caching.Providers.Redis /// public async Task GetAsync(string key, ObjectDataSet callback, T state, CancellationToken cancellation) { - _ = callback ?? throw new ArgumentNullException(nameof(callback)); + ArgumentException.ThrowIfNullOrWhiteSpace(key); + ArgumentNullException.ThrowIfNull(callback); //Try to get the value from the cache RedisValue value = await _database.StringGetAsync(key); diff --git a/plugins/VNLib.Data.Caching.Providers.Redis/src/VNLib.Data.Caching.Providers.Redis.csproj b/plugins/VNLib.Data.Caching.Providers.Redis/src/VNLib.Data.Caching.Providers.Redis.csproj index dd2d9ca..c71ee72 100644 --- a/plugins/VNLib.Data.Caching.Providers.Redis/src/VNLib.Data.Caching.Providers.Redis.csproj +++ b/plugins/VNLib.Data.Caching.Providers.Redis/src/VNLib.Data.Caching.Providers.Redis.csproj @@ -39,7 +39,7 @@ - + diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/BucketLocalManagerFactory.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/BucketLocalManagerFactory.cs index 0f49849..793e9fe 100644 --- a/plugins/VNLib.Data.Caching.Providers.VNCache/src/BucketLocalManagerFactory.cs +++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/BucketLocalManagerFactory.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: ObjectCacheServer @@ -37,6 +37,22 @@ using VNLib.Plugins.Extensions.Loading; namespace VNLib.Data.Caching.Providers.VNCache { + /* + * How it works. + * + * The built-in object cache stores allow for a memory manager to be specified for + * each bucket. Since all operations on buckets are mutually exclusive, we can + * use a single heap for each bucket to get a little more performance on memory + * operations since no locking is required. + * + * This class may be called by a dependency injection container, or directly + * created calling the Create function. Configuration may specify allocation + * flags, currently only the zero all flag is supported. + * + * By default we just use the process global heap configuration to inizalize new + * private heap instances. + */ + [ConfigurationName("memory_manager", Required = false)] internal sealed class BucketLocalManagerFactory : VnDisposeable, ICacheMemoryManagerFactory { @@ -47,9 +63,9 @@ namespace VNLib.Data.Caching.Providers.VNCache public ICacheEntryMemoryManager CreateForBucket(uint bucketId) { //Init a new heap for a individual bucket - IUnmangedHeap localHeap = MemoryUtil.InitializeNewHeapForProcess(); + IUnmangedHeap localHeap = MemoryUtil.InitializeNewHeapForProcess(_zeroAll); - BucketLocalManager manager = new (localHeap, bucketId, _zeroAll); + BucketLocalManager manager = new (localHeap, bucketId); _managers.AddLast(manager); return manager; @@ -97,16 +113,16 @@ namespace VNLib.Data.Caching.Providers.VNCache * to get a little more performance on memory operations */ - private sealed record class BucketLocalManager(IUnmangedHeap Heap, uint BucketId, bool Zero) : ICacheEntryMemoryManager + private sealed record class BucketLocalManager(IUnmangedHeap Heap, uint BucketId) : ICacheEntryMemoryManager { /// - public object AllocHandle(uint size) => Heap.Alloc(size, Zero); + public object AllocHandle(uint size) => Heap.Alloc(size, false); /// public void FreeHandle(object handle) { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); + ArgumentNullException.ThrowIfNull(handle); MemoryHandle _handle = Unsafe.As>(ref handle); //Free the handle @@ -116,7 +132,7 @@ namespace VNLib.Data.Caching.Providers.VNCache /// public uint GetHandleSize(object handle) { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); + ArgumentNullException.ThrowIfNull(handle); MemoryHandle _handle = Unsafe.As>(ref handle); return (uint)_handle.Length; @@ -125,7 +141,7 @@ namespace VNLib.Data.Caching.Providers.VNCache /// public Span GetSpan(object handle, uint offset, uint length) { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); + ArgumentNullException.ThrowIfNull(handle); MemoryHandle _handle = Unsafe.As>(ref handle); return _handle.GetOffsetSpan(offset, checked((int)length)); @@ -134,7 +150,7 @@ namespace VNLib.Data.Caching.Providers.VNCache /// public MemoryHandle PinHandle(object handle, int offset) { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); + ArgumentNullException.ThrowIfNull(handle); MemoryHandle _handle = Unsafe.As>(ref handle); //Pin the handle @@ -144,7 +160,7 @@ namespace VNLib.Data.Caching.Providers.VNCache /// public void ResizeHandle(object handle, uint newSize) { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); + ArgumentNullException.ThrowIfNull(handle); MemoryHandle _handle = Unsafe.As>(ref handle); //Resize the handle diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs index a8fb0e1..c9cd746 100644 --- a/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs +++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.Providers.VNCache @@ -48,39 +48,73 @@ namespace VNLib.Data.Caching.Providers.VNCache.Clustering public static IClusterNodeIndex CreateIndex(CacheClientConfiguration config) { - //Create a named semaphore to ensure only one index is created per app domain - using Semaphore sm = new (1, 1, APP_DOMAIN_KEY, out _); + /* TEMPORARY: + * Named semaphores are only supported on Windows, which allowed synchronized communication between + * plugins, but this is not supported on Linux. This will be replaced with a more robust solution + * in the future. For now they will just need to be separate instances. + * + * Remember while plugins are in the same app-domain, they do not share an assembly + * load context which means unless the default ALC contains the desired types, types won't unify + * so we have to use "ghetto" features to avoid interprocess communication, in the same process... + */ - if (!sm.WaitOne(500)) + if (OperatingSystem.IsWindows()) { - throw new TimeoutException("Failed to access the Cluster index shared semaphore"); - } + //Create a named semaphore to ensure only one index is created per app domain + using Semaphore sm = new (1, 1, APP_DOMAIN_KEY, out _); - try - { - //Try to get an existing index from the app domain - object? remoteIndex = AppDomain.CurrentDomain.GetData(APP_DOMAIN_KEY); - if (remoteIndex == null) + if (!sm.WaitOne(500)) + { + throw new TimeoutException("Failed to access the Cluster index shared semaphore"); + } + + try { - //Create a new index and store it in the app domain - IClusterNodeIndex index = new LocalHandler(config); - AppDomain.CurrentDomain.SetData(APP_DOMAIN_KEY, index); - return index; + //Try to get an existing index from the app domain global storage pool + object? remoteIndex = AppDomain.CurrentDomain.GetData(APP_DOMAIN_KEY); + if (remoteIndex == null) + { + //Create a new index and store it in the app domain + IClusterNodeIndex index = new LocalHandler(config); + AppDomain.CurrentDomain.SetData(APP_DOMAIN_KEY, index); + return index; + } + else + { + //Use the existing index + return new RemoteHandler(remoteIndex); + } } - else + finally { - //Use the existing index - return new RemoteHandler(remoteIndex); + sm.Release(); } } - finally + else { - sm.Release(); + return new LocalHandler(config); } } + /* + * So a bit of explaination. + * + * Plugins don't share types. Each plugin will load this package into its own ALC. Which will + * cause n instances of the cluster indext manager. Which can cause unecessary http traffic + * building the cluster index multiple times. In an attemt to avoid this, I try to share a single + * cluster index instance across all plugins in the same app domain. + * + * To do this a local handler instance is loaded into whichever plugin accuires the named semaphore + * first, and then the instance is stored in the app domain global storage pool. If its found, + * then other plugins will use the remote handler to access the index. + * + * The remote handler, attempts to use reflection to get function delegates and call the local + * handler functions via reflection. + * + * Unless VNLib.Core supports a new way to safley share types across ALCs, this is my solution. + */ - record class LocalHandler(CacheClientConfiguration Config) : IClusterNodeIndex, IIntervalScheduleable + sealed class LocalHandler(CacheClientConfiguration Config) : IClusterNodeIndex, IIntervalScheduleable { private Task _currentUpdate = Task.CompletedTask; @@ -115,18 +149,11 @@ namespace VNLib.Data.Caching.Providers.VNCache.Clustering } } - class RemoteHandler : IClusterNodeIndex + sealed class RemoteHandler(object RemoteIndex) : IClusterNodeIndex { - private readonly Func _remoteSerializer; - private readonly Func _waitTask; + private readonly Func _remoteSerializer = ManagedLibrary.GetMethod>(RemoteIndex, nameof(LocalHandler.SerializeNextNode), BindingFlags.NonPublic); - public RemoteHandler(object RemoteIndex) - { - //get the serializer method - _remoteSerializer = ManagedLibrary.GetMethod>(RemoteIndex, nameof(LocalHandler.SerializeNextNode), BindingFlags.NonPublic); - //get the wait task method - _waitTask = ManagedLibrary.GetMethod>(RemoteIndex, nameof(WaitForDiscoveryAsync), BindingFlags.Public); - } + private readonly Func _waitTask = ManagedLibrary.GetMethod>(RemoteIndex, nameof(WaitForDiscoveryAsync), BindingFlags.Public); /// public CacheNodeAdvertisment? GetNextNode() diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/FBMCacheClient.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/FBMCacheClient.cs index e21cf4a..73783dc 100644 --- a/plugins/VNLib.Data.Caching.Providers.VNCache/src/FBMCacheClient.cs +++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/FBMCacheClient.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.Providers.VNCache @@ -317,7 +317,7 @@ namespace VNLib.Data.Caching.Providers.VNCache } /// - public override Task AddOrUpdateAsync(string key, string? newKey, ObjectDataReader callback, T state, CancellationToken cancellation) + public override Task AddOrUpdateAsync(string key, string? newKey, ObjectDataGet callback, T state, CancellationToken cancellation) { return !IsConnected ? throw new InvalidOperationException("The underlying client is not connected to a cache node") diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCache.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCache.cs index 98f6a3d..0c1a2b5 100644 --- a/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCache.cs +++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCache.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.Providers.VNCache @@ -192,7 +192,7 @@ namespace VNLib.Data.Caching.Providers.VNCache } /// - public override Task AddOrUpdateAsync(string key, string? newKey, ObjectDataReader callback, T state, CancellationToken cancellation) + public override Task AddOrUpdateAsync(string key, string? newKey, ObjectDataGet callback, T state, CancellationToken cancellation) { //Update object data return _memCache.AddOrUpdateObjectAsync(key, newKey, callback, state, default, cancellation).AsTask(); diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteBackedMemoryCache.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteBackedMemoryCache.cs index c7952b4..ddc6c4b 100644 --- a/plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteBackedMemoryCache.cs +++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteBackedMemoryCache.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.Providers.VNCache @@ -83,8 +83,8 @@ namespace VNLib.Data.Caching.Providers.VNCache public RemoteBackedMemoryCache(MemoryCacheConfig memCache, IGlobalCacheProvider backingStore, BucketLocalManagerFactory? factory):base(memCache) { - _ = memCache ?? throw new ArgumentNullException(nameof(memCache)); - _ = backingStore ?? throw new ArgumentNullException(nameof(backingStore)); + ArgumentNullException.ThrowIfNull(memCache); + ArgumentNullException.ThrowIfNull(backingStore); memCache.Validate(); @@ -166,9 +166,9 @@ namespace VNLib.Data.Caching.Providers.VNCache /// public override async Task GetAsync(string key, ObjectDataSet setter, T state, CancellationToken cancellation) { - _ = key ?? throw new ArgumentNullException(nameof(key)); - _ = setter ?? throw new ArgumentNullException(nameof(setter)); - + ArgumentException.ThrowIfNullOrWhiteSpace(key); + ArgumentNullException.ThrowIfNull(setter); + CheckConnected(); IBlobCacheBucket bucket = _memCache.GetBucket(key); @@ -220,7 +220,7 @@ namespace VNLib.Data.Caching.Providers.VNCache } /// - public override async Task AddOrUpdateAsync(string key, string? newKey, ObjectDataReader callback, T state, CancellationToken cancellation) + public override async Task AddOrUpdateAsync(string key, string? newKey, ObjectDataGet callback, T state, CancellationToken cancellation) { CheckConnected(); diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheBase.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheBase.cs index f8a9ca6..c337ef4 100644 --- a/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheBase.cs +++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheBase.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.Providers.VNCache @@ -49,7 +49,7 @@ namespace VNLib.Data.Caching.Providers.VNCache public abstract Task AddOrUpdateAsync(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation); /// - public abstract Task AddOrUpdateAsync(string key, string? newKey, ObjectDataReader callback, T state, CancellationToken cancellation); + public abstract Task AddOrUpdateAsync(string key, string? newKey, ObjectDataGet callback, T state, CancellationToken cancellation); /// public abstract Task DeleteAsync(string key, CancellationToken cancellation); diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheClient.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheClient.cs index 9807939..20b9f69 100644 --- a/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheClient.cs +++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheClient.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.Providers.VNCache @@ -173,7 +173,7 @@ namespace VNLib.Data.Caching.Providers.VNCache } /// - public Task AddOrUpdateAsync(string key, string? newKey, ObjectDataReader callback, T state, CancellationToken cancellation) + public Task AddOrUpdateAsync(string key, string? newKey, ObjectDataGet callback, T state, CancellationToken cancellation) { return _client.AddOrUpdateAsync(key, newKey, callback, state, cancellation); } -- cgit