aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/VNLib.Data.Caching.Extensions/src/FBMDataCacheExtensions.cs17
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs39
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs25
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/ICacheListenerEventQueue.cs9
-rw-r--r--lib/VNLib.Data.Caching/src/ClientExtensions.cs297
-rw-r--r--lib/VNLib.Data.Caching/src/GlobalCacheExtensions.cs82
-rw-r--r--lib/VNLib.Data.Caching/src/ICacheObjectDeserializer.cs (renamed from lib/VNLib.Data.Caching/src/ICacheObjectDeserialzer.cs)6
-rw-r--r--lib/VNLib.Data.Caching/src/ICacheObjectSerializer.cs (renamed from lib/VNLib.Data.Caching/src/ICacheObjectSerialzer.cs)6
-rw-r--r--lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs43
-rw-r--r--lib/VNLib.Data.Caching/src/JsonCacheObjectSerializer.cs6
-rw-r--r--lib/VNLib.Data.Caching/src/WaitForChangeResult.cs23
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/AddOrUpdateBuffer.cs97
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs154
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/ClusterNodeIndex.cs75
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/IClusterNodeIndex.cs49
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs32
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCache.cs2
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs13
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs222
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs109
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheOperator.cs54
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs355
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/RemoteCacheOperator.cs83
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs65
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/VNLib.Plugins.Extensions.VNCache.csproj8
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs397
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClientConfig.cs139
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs188
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