diff options
Diffstat (limited to 'lib')
28 files changed, 459 insertions, 2136 deletions
diff --git a/lib/VNLib.Data.Caching.Extensions/src/FBMDataCacheExtensions.cs b/lib/VNLib.Data.Caching.Extensions/src/FBMDataCacheExtensions.cs index d53431a..708b3f5 100644 --- a/lib/VNLib.Data.Caching.Extensions/src/FBMDataCacheExtensions.cs +++ b/lib/VNLib.Data.Caching.Extensions/src/FBMDataCacheExtensions.cs @@ -101,6 +101,20 @@ namespace VNLib.Data.Caching.Extensions /// <returns>A preconfigured <see cref="FBMClientConfig"/> for object caching</returns> public static FBMClientConfig GetDefaultConfig(IUnmangedHeap heap, int maxMessageSize, TimeSpan timeout = default, ILogProvider? debugLog = null) { + return GetDefaultConfig(new FallbackFBMMemoryManager(heap), maxMessageSize, timeout, debugLog); + } + + /// <summary> + /// Gets a <see cref="FBMClientConfig"/> preconfigured object caching + /// protocl + /// </summary> + /// <param name="memManager">The client buffer heap</param> + /// <param name="maxMessageSize">The maxium message size (in bytes)</param> + /// <param name="debugLog">An optional debug log</param> + /// <param name="timeout">Request message timeout</param> + /// <returns>A preconfigured <see cref="FBMClientConfig"/> for object caching</returns> + public static FBMClientConfig GetDefaultConfig(IFBMMemoryManager memManager, int maxMessageSize, TimeSpan timeout = default, ILogProvider? debugLog = null) + { /* * Max message size (for server) should account for max data + the additional header buffer */ @@ -108,7 +122,7 @@ namespace VNLib.Data.Caching.Extensions return new() { - BufferHeap = heap, + MemoryManager = memManager, //Max message size is referrences MaxMessageSize = maxExtra, @@ -703,5 +717,6 @@ namespace VNLib.Data.Caching.Extensions int randServer = RandomNumberGenerator.GetInt32(0, servers.Count); return servers.ElementAt(randServer); } + } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs index a114236..ded89d2 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs @@ -129,7 +129,8 @@ namespace VNLib.Data.Caching.ObjectCache /// <summary> - /// Asynchronously adds or updates an object in the store and optionally update's it's id + /// Asynchronously adds or updates an object in the store and optionally update's it's id. + /// If the alternate key already exists, it's data is overwritten. /// </summary> /// <param name="table"></param> /// <param name="objectId">The current (or old) id of the object</param> @@ -143,7 +144,7 @@ namespace VNLib.Data.Caching.ObjectCache this IBlobCacheTable table, string objectId, string? alternateId, - GetBodyDataCallback<T> bodyData, + ObjectDataReader<T> bodyData, T state, DateTime time, CancellationToken cancellation = default) @@ -202,23 +203,43 @@ namespace VNLib.Data.Caching.ObjectCache { try { - //Update the handle data and reuse the entry - entry.UpdateData(bodyData(state)); + //Try to see if the alternate key already exists + if (alternateHandle.Cache.TryGetValue(alternateId, out CacheEntry existing)) + { + existing.UpdateData(bodyData(state)); - //Add the updated entry to the alternate table - alternateHandle.Cache.Add(alternateId, entry); + //dispose the old entry since we don't need it + entry.Dispose(); + } + else + { + //Update the entry buffer and reuse the entry + entry.UpdateData(bodyData(state)); + + //Add the updated entry to the alternate table + alternateHandle.Cache.Add(alternateId, entry); + } } catch { - //Cleanup handle if error adding + //Cleanup removed entry if error adding entry.Dispose(); throw; } } else { - //Old entry did not exist, we need to create a new entry for the alternate bucket - _ = alternateHandle.Cache.CreateEntry(alternateId, bodyData(state), time); + //Try to see if the alternate key already exists in the target store + if (alternateHandle.Cache.TryGetValue(alternateId, out CacheEntry existing)) + { + //overwrite the existing entry data + existing.UpdateData(bodyData(state)); + } + else + { + //Old entry did not exist, we need to create a new entry for the alternate bucket + _ = alternateHandle.Cache.CreateEntry(alternateId, bodyData(state), time); + } } } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs index d69c6bb..5139746 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs @@ -42,57 +42,54 @@ using System; using System.Threading; using System.Threading.Tasks; -using VNLib.Utils.Memory; using VNLib.Utils.Logging; +using VNLib.Net.Messaging.FBM; using VNLib.Net.Messaging.FBM.Server; using static VNLib.Data.Caching.Constants; namespace VNLib.Data.Caching.ObjectCache { - public delegate ReadOnlySpan<byte> GetBodyDataCallback<T>(T state); /// <summary> /// An <see cref="FBMListener"/> for key-value object data caching servers. /// </summary> - public class BlobCacheListener : FBMListenerBase, IDisposable + public class BlobCacheListener<T> : FBMListenerBase<T>, IDisposable { private bool disposedValue; ///<inheritdoc/> protected override ILogProvider Log { get; } + ///<inheritdoc/> + protected override FBMListener Listener { get; } /// <summary> /// A queue that stores update and delete events /// </summary> - public ICacheListenerEventQueue EventQueue { get; } + public ICacheListenerEventQueue<T> EventQueue { get; } /// <summary> /// The Cache store to access data blobs /// </summary> public IBlobCacheTable Cache { get; } - /// <summary> - /// Initialzies a new <see cref="BlobCacheListener"/> + /// Initialzies a new <see cref="BlobCacheListener{T}"/> /// </summary> /// <param name="cache">The cache table to work from</param> /// <param name="queue">The event queue to publish changes to</param> /// <param name="log">Writes error and debug logging information</param> - /// <param name="heap">The heap to alloc FBM buffers and <see cref="CacheEntry"/> cache buffers from</param> + /// <param name="memoryManager">The heap to alloc FBM buffers and <see cref="CacheEntry"/> cache buffers from</param> /// <exception cref="ArgumentNullException"></exception> - public BlobCacheListener(IBlobCacheTable cache, ICacheListenerEventQueue queue, ILogProvider log, IUnmangedHeap heap) + public BlobCacheListener(IBlobCacheTable cache, ICacheListenerEventQueue<T> queue, ILogProvider log, IFBMMemoryManager memoryManager) { - Log = log; - + Log = log; Cache = cache ?? throw new ArgumentNullException(nameof(cache)); - EventQueue = queue ?? throw new ArgumentNullException(nameof(queue)); - - InitListener(heap); + Listener = new(memoryManager); } ///<inheritdoc/> - protected override async Task ProcessAsync(FBMContext context, object? userState, CancellationToken exitToken) + protected override async Task ProcessAsync(FBMContext context, T? userState, CancellationToken exitToken) { try { diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/ICacheListenerEventQueue.cs b/lib/VNLib.Data.Caching.ObjectCache/src/ICacheListenerEventQueue.cs index 439de94..9f4146d 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/ICacheListenerEventQueue.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/ICacheListenerEventQueue.cs @@ -32,14 +32,15 @@ namespace VNLib.Data.Caching.ObjectCache /// <summary> /// Represents a single client's event queue /// </summary> - public interface ICacheListenerEventQueue + /// <typeparam name="T">The user state parameter type</typeparam> + public interface ICacheListenerEventQueue<T> { /// <summary> /// Determines if the queue is enabled for the given user state /// </summary> /// <param name="userState">The unique state of the connection</param> /// <returns>True if event queuing is enabled</returns> - bool IsEnabled([NotNullWhen(true)] object? userState); + bool IsEnabled([NotNullWhen(true)] T? userState); /// <summary> /// Attempts to dequeue a single event from the queue without blocking @@ -47,7 +48,7 @@ namespace VNLib.Data.Caching.ObjectCache /// <param name="userState">A user state object to associate with the wait operation</param> /// <param name="changeEvent">The dequeued event if successfully dequeued</param> /// <returns>True if an event was waiting and could be dequeued, false otherwise</returns> - bool TryDequeue(object userState, out ChangeEvent changeEvent); + bool TryDequeue(T userState, out ChangeEvent changeEvent); /// <summary> /// Waits asynchronously for an event to be dequeued @@ -55,7 +56,7 @@ namespace VNLib.Data.Caching.ObjectCache /// <param name="userState">A user state object to associate with the wait operation</param> /// <param name="cancellation">A token to cancel the wait operation</param> /// <returns>The <see cref="ChangeEvent"/> that as a result of the dequeue operation</returns> - ValueTask<ChangeEvent> DequeueAsync(object userState, CancellationToken cancellation); + ValueTask<ChangeEvent> DequeueAsync(T userState, CancellationToken cancellation); /// <summary> /// Publishes an event to the queue diff --git a/lib/VNLib.Data.Caching/src/ClientExtensions.cs b/lib/VNLib.Data.Caching/src/ClientExtensions.cs index 946c9b5..a2ec27d 100644 --- a/lib/VNLib.Data.Caching/src/ClientExtensions.cs +++ b/lib/VNLib.Data.Caching/src/ClientExtensions.cs @@ -24,7 +24,6 @@ using System; using System.Linq; -using System.Buffers; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -52,26 +51,7 @@ namespace VNLib.Data.Caching private static void LogDebug(this FBMClient client, string message, params object?[] args) { client.Config.DebugLog?.Debug($"[CACHE] : {message}", args); - } - - /// <summary> - /// Gets an object from the server if it exists, and uses the default serialzer to - /// recover the object - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="client"></param> - /// <param name="objectId">The id of the object to get</param> - /// <param name="cancellationToken">A token to cancel the operation</param> - /// <returns>A task that completes to return the results of the response payload</returns> - /// <exception cref="JsonException"></exception> - /// <exception cref="OutOfMemoryException"></exception> - /// <exception cref="InvalidStatusException"></exception> - /// <exception cref="ObjectDisposedException"></exception> - /// <exception cref="InvalidResponseException"></exception> - public static Task<T?> GetObjectAsync<T>(this FBMClient client, string objectId, CancellationToken cancellationToken = default) - { - return GetObjectAsync<T>(client, objectId, DefaultSerializer, cancellationToken); - } + } /// <summary> /// Updates the state of the object, and optionally updates the ID of the object. The data @@ -95,37 +75,58 @@ namespace VNLib.Data.Caching { //Use the default/json serialzer if not specified return AddOrUpdateObjectAsync(client, objectId, newId, data, DefaultSerializer, cancellationToken); - } + } /// <summary> - /// Gets an object from the server if it exists + /// 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 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="client"></param> - /// <param name="objectId">The id of the object to get</param> + /// <param name="objectId">The id of the object to update or replace</param> + /// <param name="newId">An optional parameter to specify a new ID for the old object</param> + /// <param name="data">The payload data to serialize and set as the data state of the session</param> + /// <param name="serializer">The custom serializer to used to serialze the object to binary</param> /// <param name="cancellationToken">A token to cancel the operation</param> - /// <param name="deserialzer">The custom data deserialzer used to deserialze the binary cache result</param> - /// <returns>A task that completes to return the results of the response payload</returns> + /// <returns>A task that resolves when the server responds</returns> + /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="InvalidStatusException"></exception> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="InvalidResponseException"></exception> - public static async Task<T?> GetObjectAsync<T>(this FBMClient client, string objectId, ICacheObjectDeserialzer deserialzer, CancellationToken cancellationToken = default) + /// <exception cref="MessageTooLargeException"></exception> + /// <exception cref="ObjectNotFoundException"></exception> + public static async Task AddOrUpdateObjectAsync<T>( + this FBMClient client, + string objectId, + string? newId, + T data, + ICacheObjectSerializer serializer, + CancellationToken cancellationToken = default) { _ = client ?? throw new ArgumentNullException(nameof(client)); - _ = deserialzer ?? throw new ArgumentNullException(nameof(deserialzer)); + _ = serializer ?? throw new ArgumentNullException(nameof(serializer)); - client.LogDebug("Getting object {id}", objectId); + 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.Get); + request.WriteHeader(HeaderCommand.Action, Actions.AddOrUpdate); - //Set object id header + //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(); @@ -133,22 +134,22 @@ namespace VNLib.Data.Caching //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)) + //Check status code + if (status.Value.Equals(ResponseCodes.Okay, StringComparison.OrdinalIgnoreCase)) { - return deserialzer.Deserialze<T>(response.ResponseBody); + return; } - - //Object may not exist on the server yet - if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.Ordinal)) + else if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.OrdinalIgnoreCase)) { - return default; + throw new ObjectNotFoundException($"object {objectId} not found on remote server"); } - throw new InvalidStatusException("Invalid status code recived for object get request", status.ToString()); + //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); } } @@ -157,12 +158,10 @@ namespace VNLib.Data.Caching /// 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 /// </summary> - /// <typeparam name="T"></typeparam> /// <param name="client"></param> /// <param name="objectId">The id of the object to update or replace</param> /// <param name="newId">An optional parameter to specify a new ID for the old object</param> - /// <param name="data">The payload data to serialize and set as the data state of the session</param> - /// <param name="serializer">The custom serializer to used to serialze the object to binary</param> + /// <param name="data">An <see cref="IObjectData"/> that represents the data to set</param> /// <param name="cancellationToken">A token to cancel the operation</param> /// <returns>A task that resolves when the server responds</returns> /// <exception cref="OutOfMemoryException"></exception> @@ -171,16 +170,32 @@ namespace VNLib.Data.Caching /// <exception cref="InvalidResponseException"></exception> /// <exception cref="MessageTooLargeException"></exception> /// <exception cref="ObjectNotFoundException"></exception> - public static async Task AddOrUpdateObjectAsync<T>( - this FBMClient client, - string objectId, - string? newId, - T data, - ICacheObjectSerialzer serializer, - CancellationToken cancellationToken = default) + public static Task AddOrUpdateObjectAsync(this FBMClient client, string objectId, string? newId, IObjectData data, CancellationToken cancellationToken = default) + { + return AddOrUpdateObjectAsync(client, objectId, newId, static d => d.GetData(), data, cancellationToken); + } + + /// <summary> + /// 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 + /// </summary> + /// <param name="client"></param> + /// <param name="objectId">The id of the object to update or replace</param> + /// <param name="newId">An optional parameter to specify a new ID for the old object</param> + /// <param name="callback">A callback method that will return the desired object data</param> + /// <param name="cancellationToken">A token to cancel the operation</param> + /// <param name="state">The state to be passed to the callback</param> + /// <returns>A task that resolves when the server responds</returns> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="InvalidStatusException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + /// <exception cref="InvalidResponseException"></exception> + /// <exception cref="MessageTooLargeException"></exception> + /// <exception cref="ObjectNotFoundException"></exception> + public async static Task AddOrUpdateObjectAsync<T>(this FBMClient client, string objectId, string? newId, ObjectDataReader<T> callback, T state, CancellationToken cancellationToken = default) { _ = client ?? throw new ArgumentNullException(nameof(client)); - _ = serializer ?? throw new ArgumentNullException(nameof(serializer)); + _ = callback ?? throw new ArgumentNullException(nameof(callback)); client.LogDebug("Updating object {id}, newid {nid}", objectId, newId); @@ -200,11 +215,8 @@ namespace VNLib.Data.Caching request.WriteHeader(Constants.NewObjectId, newId); } - //Get the body writer for the message - IBufferWriter<byte> bodyWriter = request.GetBodyWriter(); - - //Serialize the message - serializer.Serialize(data, bodyWriter); + //Write the message body as the objet data + request.WriteBody(callback(state)); //Make request using FBMResponse response = await client.SendAsync(request, cancellationToken); @@ -234,45 +246,70 @@ namespace VNLib.Data.Caching } /// <summary> - /// Asynchronously deletes an object in the remote store + /// Gets an object from the server if it exists, and uses the default serialzer to + /// recover the object /// </summary> + /// <typeparam name="T"></typeparam> /// <param name="client"></param> - /// <param name="objectId">The id of the object to update or replace</param> + /// <param name="objectId">The id of the object to get</param> /// <param name="cancellationToken">A token to cancel the operation</param> - /// <returns>A task that resolves when the operation has completed</returns> + /// <returns>A task that completes to return the results of the response payload</returns> + /// <exception cref="JsonException"></exception> + /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="InvalidStatusException"></exception> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="InvalidResponseException"></exception> - /// <exception cref="ObjectNotFoundException"></exception> - public static async Task DeleteObjectAsync(this FBMClient client, string objectId, CancellationToken cancellationToken = default) + public static Task<T?> GetObjectAsync<T>(this FBMClient client, string objectId, CancellationToken cancellationToken = default) + { + return GetObjectAsync<T>(client, objectId, DefaultSerializer, cancellationToken); + } + + /// <summary> + /// Gets an object from the server if it exists + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="client"></param> + /// <param name="objectId">The id of the object to get</param> + /// <param name="cancellationToken">A token to cancel the operation</param> + /// <param name="deserialzer">The custom data deserialzer used to deserialze the binary cache result</param> + /// <returns>A task that completes to return the results of the response payload</returns> + /// <exception cref="InvalidStatusException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + /// <exception cref="InvalidResponseException"></exception> + public static async Task<T?> GetObjectAsync<T>(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); - client.LogDebug("Deleting object {id}", objectId); - //Rent a new request FBMRequest request = client.RentRequest(); try { - //Set action as delete - request.WriteHeader(HeaderCommand.Action, Actions.Delete); - //Set session-id header + //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; + return deserialzer.Deserialize<T>(response.ResponseBody); } - else if(status.Value.Equals(ResponseCodes.NotFound, StringComparison.OrdinalIgnoreCase)) + + //Object may not exist on the server yet + if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.Ordinal)) { - throw new ObjectNotFoundException($"object {objectId} not found on remote server"); + return default; } throw new InvalidStatusException("Invalid status code recived for object get request", status.ToString()); @@ -284,47 +321,54 @@ namespace VNLib.Data.Caching } /// <summary> - /// 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 + /// Gets an object from the server if it exists. If data is retreived, it sets + /// the <see cref="IObjectData.SetData(ReadOnlySpan{byte})"/>, if no data is + /// found, this method returns and never calls SetData. /// </summary> /// <param name="client"></param> - /// <param name="objectId">The id of the object to update or replace</param> - /// <param name="newId">An optional parameter to specify a new ID for the old object</param> - /// <param name="data">An <see cref="IObjectData"/> that represents the data to set</param> + /// <param name="objectId">The id of the object to get</param> + /// <param name="data">An object data instance used to store the found object data</param> /// <param name="cancellationToken">A token to cancel the operation</param> - /// <returns>A task that resolves when the server responds</returns> - /// <exception cref="OutOfMemoryException"></exception> + /// <returns>A task that completes to return the results of the response payload</returns> /// <exception cref="InvalidStatusException"></exception> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="InvalidResponseException"></exception> - /// <exception cref="MessageTooLargeException"></exception> - /// <exception cref="ObjectNotFoundException"></exception> - public async static Task AddOrUpdateObjectAsync(this FBMClient client, string objectId, string? newId, IObjectData data, CancellationToken cancellationToken = default) + public static Task<bool> GetObjectAsync(this FBMClient client, string objectId, IObjectData data, CancellationToken cancellationToken = default) + { + return GetObjectAsync(client, objectId, static (p, d) => p.SetData(d), data, cancellationToken); + } + + /// <summary> + /// Gets an object from the server if it exists. If data is retreived, it sets + /// the <see cref="IObjectData.SetData(ReadOnlySpan{byte})"/>, if no data is + /// found, this method returns and never calls SetData. + /// </summary> + /// <param name="client"></param> + /// <param name="objectId">The id of the object to get</param> + /// <param name="setter">A callback method used to store the recovered object data</param> + /// <param name="state">The state parameter to pass to the callback method</param> + /// <param name="cancellationToken">A token to cancel the operation</param> + /// <returns>When complete, true if the object was found, false if not found, and an exception otherwise</returns> + /// <exception cref="InvalidStatusException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + /// <exception cref="InvalidResponseException"></exception> + public static async Task<bool> GetObjectAsync<T>(this FBMClient client, string objectId, ObjectDataSet<T> setter, T state, CancellationToken cancellationToken = default) { _ = client ?? throw new ArgumentNullException(nameof(client)); - _ = data ?? throw new ArgumentNullException(nameof(data)); + _ = setter ?? throw new ArgumentNullException(nameof(setter)); - client.LogDebug("Updating object {id}, newid {nid}", objectId, newId); + 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.AddOrUpdate); + request.WriteHeader(HeaderCommand.Action, Actions.Get); - //Set session-id header + //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); - } - - //Write the message body as the objet data - request.WriteBody(data.GetData()); - //Make request using FBMResponse response = await client.SendAsync(request, cancellationToken); response.ThrowIfNotSet(); @@ -332,54 +376,52 @@ namespace VNLib.Data.Caching //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)) + //Check ok status code, then its safe to deserialize + if (status.Value.Equals(ResponseCodes.Okay, StringComparison.Ordinal)) { - return; + //Write the object data + setter(state, response.ResponseBody); + return true; } - else if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.OrdinalIgnoreCase)) + + //Object may not exist on the server yet + if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.Ordinal)) { - throw new ObjectNotFoundException($"object {objectId} not found on remote server"); + return false; } - //Invalid status - throw new InvalidStatusException("Invalid status code recived for object upsert request", status.ToString()); + throw new InvalidStatusException("Invalid status code recived for object get request", status.ToString()); } finally { - //Return the request(clears data and reset) client.ReturnRequest(request); } } /// <summary> - /// Gets an object from the server if it exists. If data is retreived, it sets - /// the <see cref="IObjectData.SetData(ReadOnlySpan{byte})"/>, if no data is - /// found, this method returns and never calls SetData. + /// Asynchronously deletes an object in the remote store /// </summary> /// <param name="client"></param> - /// <param name="objectId">The id of the object to get</param> + /// <param name="objectId">The id of the object to update or replace</param> /// <param name="cancellationToken">A token to cancel the operation</param> - /// <param name="data">An <see cref="IObjectData"/> that represents the object data to set</param> - /// <returns>A task that completes to return the results of the response payload</returns> + /// <returns>A task that resolves when the operation has completed</returns> /// <exception cref="InvalidStatusException"></exception> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="InvalidResponseException"></exception> - public static async Task GetObjectAsync(this FBMClient client, string objectId, IObjectData data, CancellationToken cancellationToken = default) + /// <exception cref="ObjectNotFoundException"></exception> + public static async Task<bool> DeleteObjectAsync(this FBMClient client, string objectId, CancellationToken cancellationToken = default) { _ = client ?? throw new ArgumentNullException(nameof(client)); - _ = data ?? throw new ArgumentNullException(nameof(data)); - client.LogDebug("Getting object {id}", objectId); + client.LogDebug("Deleting 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 + //Set action as delete + request.WriteHeader(HeaderCommand.Action, Actions.Delete); + //Set session-id header request.WriteHeader(Constants.ObjectId, objectId); //Make request @@ -389,18 +431,13 @@ namespace VNLib.Data.Caching //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)) { - //Write the object data - data.SetData(response.ResponseBody); - return; + return true; } - - //Object may not exist on the server yet - if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.Ordinal)) + else if (status.Value.Equals(ResponseCodes.NotFound, StringComparison.OrdinalIgnoreCase)) { - return; + return false; } throw new InvalidStatusException("Invalid status code recived for object get request", status.ToString()); @@ -415,10 +452,15 @@ namespace VNLib.Data.Caching /// Dequeues a change event from the server event queue for the current connection, or waits until a change happens /// </summary> /// <param name="client"></param> + /// <param name="change">The instance to store change event data to</param> /// <param name="cancellationToken">A token to cancel the deuque operation</param> /// <returns>A <see cref="WaitForChangeResult"/> that contains information about the modified element</returns> - public static async Task<WaitForChangeResult> WaitForChangeAsync(this FBMClient client, CancellationToken cancellationToken = default) + /// <exception cref="InvalidResponseException"></exception> + /// <exception cref="InvalidOperationException"></exception> + public static async Task WaitForChangeAsync(this FBMClient client, WaitForChangeResult change, CancellationToken cancellationToken = default) { + _ = change ?? throw new ArgumentNullException(nameof(change)); + //Rent a new request FBMRequest request = client.RentRequest(); try @@ -431,12 +473,9 @@ namespace VNLib.Data.Caching response.ThrowIfNotSet(); - return new() - { - Status = response.Headers.FirstOrDefault(static a => a.Header == HeaderCommand.Status).Value.ToString(), - CurrentId = response.Headers.SingleOrDefault(static v => v.Header == Constants.ObjectId).Value.ToString(), - NewId = response.Headers.SingleOrDefault(static v => v.Header == Constants.NewObjectId).Value.ToString() - }; + 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(); } finally { diff --git a/lib/VNLib.Data.Caching/src/GlobalCacheExtensions.cs b/lib/VNLib.Data.Caching/src/GlobalCacheExtensions.cs new file mode 100644 index 0000000..8b23240 --- /dev/null +++ b/lib/VNLib.Data.Caching/src/GlobalCacheExtensions.cs @@ -0,0 +1,82 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Data.Caching +* File: GlobalCacheExtensions.cs +* +* GlobalCacheExtensions.cs is part of VNLib.Data.Caching which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Data.Caching 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 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; + +#pragma warning disable CA1062 // Validate arguments of public methods + +namespace VNLib.Data.Caching +{ + /// <summary> + /// Exports extension methods for the <see cref="IGlobalCacheProvider"/> interface + /// </summary> + public static class GlobalCacheExtensions + { + /// <summary> + /// Asynchronously gets a value from the backing cache store and writes it to the + /// supplied data buffer + /// </summary> + /// <param name="cache"></param> + /// <param name="key">The key identifying the object to recover from cache</param> + /// <param name="rawData">The </param> + /// <param name="cancellation">A token to cancel the async operation</param> + /// <returns>A task that complets when the object data has been written to the data buffer</returns> + public static Task GetAsync(this IGlobalCacheProvider cache, string key, IObjectData rawData, CancellationToken cancellation) + { + return cache.GetAsync(key, static (cd, data) => cd.SetData(data), rawData, cancellation); + } + + /// <summary> + /// Asynchronously sets (or updates) a cached value in the backing cache store + /// from the supplied raw data + /// </summary> + /// <param name="cache"></param> + /// <param name="key">The key identifying the object to recover from cache</param> + /// <param name="newKey">An optional key that will be changed for the new object</param> + /// <param name="cancellation">A token to cancel the async operation</param> + /// <param name="rawData">The raw data to store at the given key</param> + /// <returns>A task that completes when the update operation has compelted</returns> + public static Task AddOrUpdateAsync(this IGlobalCacheProvider cache, string key, string? newKey, IObjectData rawData, CancellationToken cancellation) + { + return cache.AddOrUpdateAsync(key, newKey, static cd => cd.GetData(), rawData, cancellation); + } + + /// <summary> + /// Asynchronously sets (or updates) a cached value in the backing cache store + /// from the supplied raw data + /// </summary> + /// <param name="cache"></param> + /// <param name="key">The key identifying the object to recover from cache</param> + /// <param name="newKey">An optional key that will be changed for the new object</param> + /// <param name="cancellation">A token to cancel the async operation</param> + /// <param name="rawData">The raw data to store at the given key</param> + /// <returns>A task that completes when the update operation has compelted</returns> + public static Task AddOrUpdateAsync(this IGlobalCacheProvider cache, string key, string? newKey, ReadOnlyMemory<byte> rawData, CancellationToken cancellation) + { + return cache.AddOrUpdateAsync(key, newKey, static cd => cd.Span, rawData, cancellation); + } + } +}
\ No newline at end of file diff --git a/lib/VNLib.Data.Caching/src/ICacheObjectDeserialzer.cs b/lib/VNLib.Data.Caching/src/ICacheObjectDeserializer.cs index 3cdb395..560d3bf 100644 --- a/lib/VNLib.Data.Caching/src/ICacheObjectDeserialzer.cs +++ b/lib/VNLib.Data.Caching/src/ICacheObjectDeserializer.cs @@ -3,7 +3,7 @@ * * Library: VNLib * Package: VNLib.Data.Caching -* File: ICacheObjectDeserialzer.cs +* File: ICacheObjectDeserializer.cs * * ICacheObjectDeserialzer.cs is part of VNLib.Data.Caching which is part * of the larger VNLib collection of libraries and utilities. @@ -29,7 +29,7 @@ namespace VNLib.Data.Caching /// <summary> /// Provides custom binary deserialzation for a given type /// </summary> - public interface ICacheObjectDeserialzer + public interface ICacheObjectDeserializer { /// <summary> /// Attempts to deserialze the supplied binary buffer to its original @@ -38,6 +38,6 @@ namespace VNLib.Data.Caching /// <param name="objectData">The buffer containing data to deserialze</param> /// <typeparam name="T"></typeparam> /// <returns>A new instance deserialzed to contain the original entity state</returns> - T? Deserialze<T>(ReadOnlySpan<byte> objectData); + T? Deserialize<T>(ReadOnlySpan<byte> objectData); } } diff --git a/lib/VNLib.Data.Caching/src/ICacheObjectSerialzer.cs b/lib/VNLib.Data.Caching/src/ICacheObjectSerializer.cs index fba2df0..658f63e 100644 --- a/lib/VNLib.Data.Caching/src/ICacheObjectSerialzer.cs +++ b/lib/VNLib.Data.Caching/src/ICacheObjectSerializer.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Data.Caching -* File: ICacheObjectSerialzer.cs +* File: ICacheObjectSerializer.cs * -* ICacheObjectSerialzer.cs is part of VNLib.Data.Caching which is part +* ICacheObjectSerializer.cs is part of VNLib.Data.Caching which is part * of the larger VNLib collection of libraries and utilities. * * VNLib.Data.Caching is free software: you can redistribute it and/or modify @@ -30,7 +30,7 @@ namespace VNLib.Data.Caching /// <summary> /// Provides custom binary deserialziation for a given type /// </summary> - public interface ICacheObjectSerialzer + public interface ICacheObjectSerializer { /// <summary> /// Serializes an instance of the given type and writes diff --git a/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs b/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs index 8a857d4..18c8a98 100644 --- a/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs +++ b/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs @@ -22,11 +22,30 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ +using System; using System.Threading; using System.Threading.Tasks; namespace VNLib.Data.Caching { + + /// <summary> + /// A delegate method that will set the raw object data on the state object + /// if data was found + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="state">The state passed to the original call</param> + /// <param name="objectData">The raw data of the cached object</param> + public delegate void ObjectDataSet<T>(T state, ReadOnlySpan<byte> objectData); + + /// <summary> + /// A delegate method that will get the raw objet data from a state object + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="state">The state object passed to the caller</param> + /// <returns>The raw object data to store in cache</returns> + public delegate ReadOnlySpan<byte> ObjectDataReader<T>(T state); + /// <summary> /// A global cache provider interface /// </summary> @@ -38,6 +57,12 @@ namespace VNLib.Data.Caching bool IsConnected { get; } /// <summary> + /// Gets the underlying cache store object + /// </summary> + /// <returns>The underlying cache store instance</returns> + object GetUnderlyingStore(); + + /// <summary> /// Asynchronously gets a value from the backing cache store /// </summary> /// <typeparam name="T"></typeparam> @@ -63,7 +88,7 @@ namespace VNLib.Data.Caching /// <param name="key">The key identifying the item to delete</param> /// <param name="cancellation">A token to cancel the async operation</param> /// <returns>A task that completes when the delete operation has compelted</returns> - Task DeleteAsync(string key, CancellationToken cancellation); + Task<bool> DeleteAsync(string key, CancellationToken cancellation); /// <summary> /// Asynchronously gets a value from the backing cache store @@ -73,7 +98,7 @@ namespace VNLib.Data.Caching /// <param name="deserializer">The specific deserialzer to deserialze the object</param> /// <param name="cancellation">A token to cancel the async operation</param> /// <returns>The value if found, or null if it does not exist in the store</returns> - Task<T?> GetAsync<T>(string key, ICacheObjectDeserialzer deserializer, CancellationToken cancellation); + Task<T?> GetAsync<T>(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation); /// <summary> /// Asynchronously sets (or updates) a cached value in the backing cache store @@ -83,19 +108,20 @@ namespace VNLib.Data.Caching /// <param name="newKey">An optional key that will be changed for the new object</param> /// <param name="cancellation">A token to cancel the async operation</param> /// <param name="value">The value to set at the given key</param> - /// <param name="serialzer">The <see cref="ICacheObjectSerialzer"/> used to serialze the entity</param> + /// <param name="serialzer">The <see cref="ICacheObjectSerializer"/> used to serialze the entity</param> /// <returns>A task that completes when the update operation has compelted</returns> - Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation); + Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation); /// <summary> /// Asynchronously gets a value from the backing cache store and writes it to the /// supplied data buffer /// </summary> /// <param name="key">The key identifying the object to recover from cache</param> - /// <param name="rawData">The </param> + /// <param name="callback">The callback method that will get the raw object data</param> + /// <param name="state">The state parameter to pass to the callback when invoked</param> /// <param name="cancellation">A token to cancel the async operation</param> /// <returns>A task that complets when the object data has been written to the data buffer</returns> - Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation); + Task GetAsync<T>(string key, ObjectDataSet<T> callback, T state, CancellationToken cancellation); /// <summary> /// Asynchronously sets (or updates) a cached value in the backing cache store @@ -103,9 +129,10 @@ namespace VNLib.Data.Caching /// </summary> /// <param name="key">The key identifying the object to recover from cache</param> /// <param name="newKey">An optional key that will be changed for the new object</param> + /// <param name="callback">A callback method that will set the raw object data when received</param> + /// <param name="state">The callback state parameter</param> /// <param name="cancellation">A token to cancel the async operation</param> - /// <param name="rawData">The raw data to store at the given key</param> /// <returns>A task that completes when the update operation has compelted</returns> - Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation); + Task AddOrUpdateAsync<T>(string key, string? newKey, ObjectDataReader<T> callback, T state, CancellationToken cancellation); } }
\ No newline at end of file diff --git a/lib/VNLib.Data.Caching/src/JsonCacheObjectSerializer.cs b/lib/VNLib.Data.Caching/src/JsonCacheObjectSerializer.cs index dce0bdf..85d1184 100644 --- a/lib/VNLib.Data.Caching/src/JsonCacheObjectSerializer.cs +++ b/lib/VNLib.Data.Caching/src/JsonCacheObjectSerializer.cs @@ -32,10 +32,10 @@ using VNLib.Utils.Memory.Caching; namespace VNLib.Data.Caching { /// <summary> - /// Implements a <see cref="ICacheObjectDeserialzer"/> and a <see cref="ICacheObjectSerialzer"/> + /// Implements a <see cref="ICacheObjectDeserializer"/> and a <see cref="ICacheObjectSerializer"/> /// that uses JSON serialization, with writer pooling. Members of this class are thread-safe. /// </summary> - public class JsonCacheObjectSerializer : ICacheObjectSerialzer, ICacheObjectDeserialzer + public class JsonCacheObjectSerializer : ICacheObjectSerializer, ICacheObjectDeserializer { //Create threadlocal writer for attempted lock-free writer reuse private static readonly ObjectRental<ReusableJsonWriter> JsonWriterPool = ObjectRental.CreateThreadLocal<ReusableJsonWriter>(); @@ -75,7 +75,7 @@ namespace VNLib.Data.Caching } ///<inheritdoc/> - public virtual T? Deserialze<T>(ReadOnlySpan<byte> objectData) => JsonSerializer.Deserialize<T>(objectData, _options); + public virtual T? Deserialize<T>(ReadOnlySpan<byte> objectData) => JsonSerializer.Deserialize<T>(objectData, _options); ///<inheritdoc/> public virtual void Serialize<T>(T obj, IBufferWriter<byte> finiteWriter) diff --git a/lib/VNLib.Data.Caching/src/WaitForChangeResult.cs b/lib/VNLib.Data.Caching/src/WaitForChangeResult.cs index fe4f22f..18428ce 100644 --- a/lib/VNLib.Data.Caching/src/WaitForChangeResult.cs +++ b/lib/VNLib.Data.Caching/src/WaitForChangeResult.cs @@ -27,14 +27,21 @@ namespace VNLib.Data.Caching /// <summary> /// The result of a cache server change event /// </summary> - /// <param name="Status">The operation status code</param> - /// <param name="CurrentId">The current (or old) id of the element that changed</param> - /// <param name="NewId">The new id of the element that changed</param> - public readonly record struct WaitForChangeResult( - string Status, - string CurrentId, - string NewId) + public sealed record class WaitForChangeResult { - + /// <summary> + /// The operation status code + /// </summary> + public string? Status { get; set; } + + /// <summary> + /// The current (or old) id of the element that changed + /// </summary> + public string? CurrentId { get; set; } + + /// <summary> + /// The new id of the element that changed + /// </summary> + public string? NewId { get; set; } } } 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 |