aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-11-02 01:50:05 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-11-02 01:50:05 -0400
commitd2d812213b99ee17f9433f81871b694c4053ff23 (patch)
tree11a1106602112c134e65bf197ef701d1b8d63b67
parent483c014b938e2d55ea7c89b67f6d19ba2c2d5b5e (diff)
also carried away
-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/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/RemoteBackedMemoryCache.cs355
-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--plugins/ObjectCacheServer/src/Cache/CacheListenerPubQueue.cs12
-rw-r--r--plugins/ObjectCacheServer/src/Cache/CacheStore.cs13
-rw-r--r--plugins/ObjectCacheServer/src/Clustering/CacheNodeReplicationMaanger.cs18
-rw-r--r--plugins/ObjectCacheServer/src/Endpoints/CacheNegotationManager.cs207
-rw-r--r--plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs206
-rw-r--r--plugins/ObjectCacheServer/src/ICacheStore.cs2
-rw-r--r--plugins/VNLib.Data.Caching.Providers.Redis/README.md18
-rw-r--r--plugins/VNLib.Data.Caching.Providers.Redis/build.readme.md0
-rw-r--r--plugins/VNLib.Data.Caching.Providers.Redis/src/RedisClientCacheEntry.cs380
-rw-r--r--plugins/VNLib.Data.Caching.Providers.Redis/src/VNLib.Data.Caching.Providers.Redis.csproj54
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/README.md18
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/build.readme.md0
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/AddOrUpdateBuffer.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/AddOrUpdateBuffer.cs)29
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/BucketLocalManagerFactory.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs)7
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/ClusterNodeIndex.cs)10
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/IClusterNodeIndex.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/IClusterNodeIndex.cs)10
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/FBMCacheClient.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs)79
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/ICacheRefreshPolicy.cs35
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCache.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs)66
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCacheConfig.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs)18
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCacheOperator.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheOperator.cs)12
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteBackedMemoryCache.cs350
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteCacheOperator.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/RemoteCacheOperator.cs)24
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheClient.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs)93
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/VNLib.Data.Caching.Providers.VNCache.csproj52
-rw-r--r--plugins/VNLib.Data.Caching.Providers.VNCache/src/VnCacheClientConfig.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClientConfig.cs)20
-rw-r--r--vnlib.data.caching.build.sln14
44 files changed, 1856 insertions, 919 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/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/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/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/plugins/ObjectCacheServer/src/Cache/CacheListenerPubQueue.cs b/plugins/ObjectCacheServer/src/Cache/CacheListenerPubQueue.cs
index ba39db6..6942828 100644
--- a/plugins/ObjectCacheServer/src/Cache/CacheListenerPubQueue.cs
+++ b/plugins/ObjectCacheServer/src/Cache/CacheListenerPubQueue.cs
@@ -43,7 +43,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Cache
* their individual queues.
*/
- internal sealed class CacheListenerPubQueue : ICacheListenerEventQueue, IAsyncBackgroundWork
+ internal sealed class CacheListenerPubQueue : ICacheListenerEventQueue<IPeerEventQueue>, IAsyncBackgroundWork
{
private const int MAX_LOCAL_QUEUE_ITEMS = 10000;
private const string LOG_SCOPE_NAME = "QUEUE";
@@ -110,7 +110,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Cache
}
///<inheritdoc/>
- public bool IsEnabled([NotNullWhen(true)] object? userState)
+ public bool IsEnabled([NotNullWhen(true)] IPeerEventQueue? userState)
{
return userState is IPeerEventQueue;
}
@@ -125,15 +125,15 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Cache
}
///<inheritdoc/>
- public bool TryDequeue(object userState, out ChangeEvent changeEvent)
+ public bool TryDequeue(IPeerEventQueue userState, out ChangeEvent changeEvent)
{
- return (userState as IPeerEventQueue)!.TryDequeue(out changeEvent);
+ return userState.TryDequeue(out changeEvent);
}
///<inheritdoc/>
- public ValueTask<ChangeEvent> DequeueAsync(object userState, CancellationToken cancellation)
+ public ValueTask<ChangeEvent> DequeueAsync(IPeerEventQueue userState, CancellationToken cancellation)
{
- return (userState as IPeerEventQueue)!.DequeueAsync(cancellation);
+ return userState.DequeueAsync(cancellation);
}
}
}
diff --git a/plugins/ObjectCacheServer/src/Cache/CacheStore.cs b/plugins/ObjectCacheServer/src/Cache/CacheStore.cs
index 5795222..02ed9b1 100644
--- a/plugins/ObjectCacheServer/src/Cache/CacheStore.cs
+++ b/plugins/ObjectCacheServer/src/Cache/CacheStore.cs
@@ -27,6 +27,7 @@ using System.Threading;
using System.Threading.Tasks;
using VNLib.Utils.Logging;
+using VNLib.Net.Messaging.FBM;
using VNLib.Plugins;
using VNLib.Plugins.Extensions.Loading;
@@ -43,7 +44,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Cache
/// <summary>
/// Gets the underlying cache listener
/// </summary>
- public BlobCacheListener Listener { get; }
+ public BlobCacheListener<IPeerEventQueue> Listener { get; }
public CacheStore(PluginBase plugin, IConfigScope config)
@@ -53,7 +54,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Cache
}
///<inheritdoc/>
- ValueTask ICacheStore.AddOrUpdateBlobAsync<T>(string objectId, string? alternateId, GetBodyDataCallback<T> bodyData, T state, CancellationToken token)
+ ValueTask ICacheStore.AddOrUpdateBlobAsync<T>(string objectId, string? alternateId, ObjectDataReader<T> bodyData, T state, CancellationToken token)
{
return Listener.Cache.AddOrUpdateObjectAsync(objectId, alternateId, bodyData, state, default, token);
}
@@ -70,7 +71,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Cache
return Listener.Cache.DeleteObjectAsync(id, token);
}
- private static BlobCacheListener InitializeCache(ObjectCacheServerEntry plugin, IConfigScope config)
+ private static BlobCacheListener<IPeerEventQueue> InitializeCache(ObjectCacheServerEntry plugin, IConfigScope config)
{
const string CacheConfigTemplate =
@"
@@ -105,7 +106,7 @@ Cache Configuration:
);
//Get the event listener
- ICacheListenerEventQueue queue = plugin.GetOrCreateSingleton<CacheListenerPubQueue>();
+ ICacheListenerEventQueue<IPeerEventQueue> queue = plugin.GetOrCreateSingleton<CacheListenerPubQueue>();
//Get the memory manager
ICacheMemoryManagerFactory manager = plugin.GetOrCreateSingleton<BucketLocalManagerFactory>();
@@ -113,8 +114,10 @@ Cache Configuration:
//Load the blob cache table system
IBlobCacheTable bc = plugin.LoadMemoryCacheSystem(config, manager, cacheConf);
+ FallbackFBMMemoryManager fbmMemManager = new(plugin.ListenerHeap);
+
//Endpoint only allows for a single reader
- return new(bc, queue, plugin.Log, plugin.ListenerHeap);
+ return new(bc, queue, plugin.Log, fbmMemManager);
}
/*
diff --git a/plugins/ObjectCacheServer/src/Clustering/CacheNodeReplicationMaanger.cs b/plugins/ObjectCacheServer/src/Clustering/CacheNodeReplicationMaanger.cs
index 19f09dc..dbfd091 100644
--- a/plugins/ObjectCacheServer/src/Clustering/CacheNodeReplicationMaanger.cs
+++ b/plugins/ObjectCacheServer/src/Clustering/CacheNodeReplicationMaanger.cs
@@ -231,18 +231,25 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Clustering
private async Task ReplicationWorkerDoWorkAsync(FBMClient client, ILogProvider log, CancellationToken exitToken)
{
//Reusable request message
- using FBMRequest request = new(client.Config);
+ using FBMRequest request = new(in client.Config);
+
+ WaitForChangeResult changedObject = new();
//Listen for changes
while (true)
{
//Wait for changes
- WaitForChangeResult changedObject = await client.WaitForChangeAsync(exitToken);
+ await client.WaitForChangeAsync(changedObject, exitToken);
log.Debug("Object changed {typ} {obj}", changedObject.Status, changedObject.CurrentId);
switch (changedObject.Status)
{
+ /*
+ * During a WFC operation, if a NotFound response is received, it
+ * means a wait queue was not found for the connection, usually meaning
+ * the server does not support replication.
+ */
case ResponseCodes.NotFound:
log.Error("Server cache not properly configured, worker exiting");
return;
@@ -254,8 +261,15 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Clustering
//Reload the record from the store
await UpdateRecordAsync(client, request, log, changedObject.CurrentId, changedObject.NewId, exitToken);
break;
+ default:
+ log.Error("Unknown status {status} received from server", changedObject.Status);
+ break;
}
+ changedObject.Status = null;
+ changedObject.CurrentId = null;
+ changedObject.NewId = null;
+
//Reset request message
request.Reset();
}
diff --git a/plugins/ObjectCacheServer/src/Endpoints/CacheNegotationManager.cs b/plugins/ObjectCacheServer/src/Endpoints/CacheNegotationManager.cs
new file mode 100644
index 0000000..d1591f8
--- /dev/null
+++ b/plugins/ObjectCacheServer/src/Endpoints/CacheNegotationManager.cs
@@ -0,0 +1,207 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: ObjectCacheServer
+* File: CacheNegotationManager.cs
+*
+* CacheNegotationManager.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.Net;
+using System.Text.Json;
+
+using VNLib.Hashing;
+using VNLib.Hashing.IdentityUtility;
+using VNLib.Net.Messaging.FBM.Client;
+using VNLib.Plugins;
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Data.Caching.Extensions;
+using VNLib.Data.Caching.ObjectCache.Server.Cache;
+
+namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
+{
+ internal class ClientNegotiationState
+ {
+ public string? Challenge { get; set; }
+
+ public string? NodeId { get; set; }
+
+ public bool IsPeer { get; set; }
+ }
+
+ internal sealed class CacheNegotationManager
+ {
+ /*
+ * Cache keys are centralized and may be shared between all cache server nodes. This means
+ * that any client would be able to get a signed negotiation from any server and use it to
+ * upgrade a connection to any other server. This is property is to be avoided because servers
+ * may have different configurations that a malicious client could exploit. To prevent that
+ * a unique server generated Audience ID is used in the negotiation token and verified when
+ * an upgrade is requested. This ensures that the client is connecting to the same server
+ * that issued the negotiation token.
+ *
+ * With this operational theory, someone has to expose their buffer configuration. At the moment
+ * I think it would be best for servers to keep their buffer configuration private, as it could
+ * cause more damage to the network. This is not really a protection measure because a malicious
+ * client could use trial and error to find the servers buffer configuration.
+ */
+
+ private static readonly TimeSpan AuthTokenExpiration = TimeSpan.FromSeconds(30);
+
+ private readonly string AudienceLocalServerId;
+ private readonly NodeConfig _nodeConfig;
+ private readonly CacheConfiguration _cacheConfig;
+
+ public CacheNegotationManager(PluginBase plugin)
+ {
+ //Get node configuration
+ _nodeConfig = plugin.GetOrCreateSingleton<NodeConfig>();
+
+ //Get the cache store configuration
+ _cacheConfig = plugin.GetConfigForType<CacheStore>().Deserialze<CacheConfiguration>();
+
+ AudienceLocalServerId = Guid.NewGuid().ToString("N");
+ }
+
+
+ public bool IsClientNegotiationValid(string authToken, out ClientNegotiationState state)
+ {
+ state = new();
+
+ // Parse jwt
+ using JsonWebToken jwt = JsonWebToken.Parse(authToken);
+
+ //verify signature for client
+ if (_nodeConfig.KeyStore.VerifyJwt(jwt, false))
+ {
+ //Validated as normal client
+ }
+ //May be signed by a cache server
+ else if (_nodeConfig.KeyStore.VerifyJwt(jwt, true))
+ {
+ //Set peer and verified flag since the another cache server signed the request
+ state.IsPeer = true;
+ }
+ else
+ {
+ return false;
+ }
+
+ //Recover json body
+ using JsonDocument doc = jwt.GetPayload();
+
+ if (doc.RootElement.TryGetProperty("sub", out JsonElement servIdEl))
+ {
+ state.NodeId = servIdEl.GetString();
+ }
+
+ //Challenge is required
+ state.Challenge = doc.RootElement.GetProperty("chl").GetString()!;
+
+ return true;
+ }
+
+ public JsonWebToken ConfirmCLientNegotiation(ClientNegotiationState state, IPAddress clientIp, DateTimeOffset now)
+ {
+ //Verified, now we can create an auth message with a short expiration
+ JsonWebToken auth = new();
+
+ auth.WriteHeader(_nodeConfig.KeyStore.GetJwtHeader());
+ auth.InitPayloadClaim()
+ .AddClaim("aud", AudienceLocalServerId)
+ .AddClaim("iat", now.ToUnixTimeSeconds())
+ .AddClaim("exp", now.Add(AuthTokenExpiration).ToUnixTimeSeconds())
+ .AddClaim("nonce", RandomHash.GetRandomBase32(8))
+ .AddClaim("chl", state.Challenge!)
+ //Set the ispeer flag if the request was signed by a cache server
+ .AddClaim("isPeer", state.IsPeer)
+ //Specify the server's node id if set
+ .AddClaim("sub", state.NodeId)
+ //Set ip address
+ .AddClaim("ip", clientIp.ToString())
+ //Add negotiaion args
+ .AddClaim(FBMClient.REQ_HEAD_BUF_QUERY_ARG, _cacheConfig.MaxHeaderBufferSize)
+ .AddClaim(FBMClient.REQ_RECV_BUF_QUERY_ARG, _cacheConfig.MaxRecvBufferSize)
+ .AddClaim(FBMClient.REQ_MAX_MESS_QUERY_ARG, _cacheConfig.MaxMessageSize)
+ .CommitClaims();
+
+ //Sign the auth message from our private key
+ _nodeConfig.KeyStore.SignJwt(auth);
+
+ return auth;
+ }
+
+ public bool ValidateUpgrade(string upgradeToken, string tokenSignature, DateTimeOffset now, IPAddress connectionIp, ref string? nodeId, ref bool isPeer)
+ {
+ //Parse jwt
+ using JsonWebToken jwt = JsonWebToken.Parse(upgradeToken);
+
+ //verify signature against the cache public key, since this server must have signed it
+ if (!_nodeConfig.KeyStore.VerifyCachePeer(jwt))
+ {
+ return false;
+ }
+
+ //Recover json body
+ using JsonDocument doc = jwt.GetPayload();
+
+ //Verify audience, expiration
+ if (!doc.RootElement.TryGetProperty("aud", out JsonElement audEl)
+ || !string.Equals(AudienceLocalServerId, audEl.GetString(), StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ if (!doc.RootElement.TryGetProperty("exp", out JsonElement expEl)
+ || DateTimeOffset.FromUnixTimeSeconds(expEl.GetInt64()) < now)
+ {
+ return false;
+ }
+
+ //Check node ip address matches if required
+ if (_nodeConfig.VerifyIp)
+ {
+ if (!doc.RootElement.TryGetProperty("ip", out JsonElement ipEl))
+ {
+ return false;
+ }
+
+ string? clientIp = ipEl.GetString();
+
+ //Verify the client ip address matches the one in the token
+ if (clientIp == null || !IPAddress.TryParse(clientIp, out IPAddress? clientIpAddr) || !clientIpAddr.Equals(connectionIp))
+ {
+ return false;
+ }
+ }
+
+ //Check if the client is a peer
+ isPeer = doc.RootElement.TryGetProperty("isPeer", out JsonElement isPeerEl) && isPeerEl.GetBoolean();
+
+ //The node id is optional and stored in the 'sub' field, ignore if the client is not a peer
+ if (isPeer && doc.RootElement.TryGetProperty("sub", out JsonElement servIdEl))
+ {
+ nodeId = servIdEl.GetString();
+ }
+
+ //Verify token signature against a fellow cache public key
+ return _nodeConfig.KeyStore.VerifyUpgradeToken(tokenSignature, upgradeToken, isPeer);
+ }
+ }
+}
diff --git a/plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs b/plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs
index d07c59e..816e6c3 100644
--- a/plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs
+++ b/plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs
@@ -24,12 +24,10 @@
using System;
using System.Net;
-using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
-using VNLib.Hashing;
using VNLib.Net.Http;
using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
@@ -49,22 +47,20 @@ using VNLib.Data.Caching.Extensions.Clustering;
using VNLib.Data.Caching.ObjectCache.Server.Cache;
using VNLib.Data.Caching.ObjectCache.Server.Clustering;
+
namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
{
internal sealed class ConnectEndpoint : ResourceEndpointBase
{
- private const string LOG_SCOPE_NAME = "CONEP";
-
- private static readonly TimeSpan AuthTokenExpiration = TimeSpan.FromSeconds(30);
-
+ internal const string LOG_SCOPE_NAME = "CONEP";
- private readonly NodeConfig NodeConfiguration;
+
private readonly ICacheEventQueueManager PubSubManager;
private readonly IPeerMonitor Peers;
- private readonly BlobCacheListener Store;
-
- private readonly string AudienceLocalServerId;
+ private readonly BlobCacheListener<IPeerEventQueue> Store;
+ private readonly NodeConfig NodeConfiguration;
+ private readonly CacheNegotationManager AuthManager;
private uint _connectedClients;
@@ -105,11 +101,8 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
//Get the cache store configuration
CacheConfig = plugin.GetConfigForType<CacheStore>().Deserialze<CacheConfiguration>();
- /*
- * Generate a random guid for the current server when created so we
- * know client tokens belong to us when singed by the same key
- */
- AudienceLocalServerId = Guid.NewGuid().ToString("N");
+ //Get the auth manager
+ AuthManager = plugin.GetOrCreateSingleton<CacheNegotationManager>();
}
@@ -136,52 +129,19 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
string? jwtAuth = entity.Server.Headers[HttpRequestHeader.Authorization];
if (string.IsNullOrWhiteSpace(jwtAuth))
{
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
+ return VirtualClose(entity, HttpStatusCode.Forbidden);
}
- string? nodeId = null;
- string? challenge = null;
- bool isPeer = false;
-
- // Parse jwt
- using (JsonWebToken jwt = JsonWebToken.Parse(jwtAuth))
+ //Create negotiation state
+ if(!AuthManager.IsClientNegotiationValid(jwtAuth, out ClientNegotiationState state))
{
- //verify signature for client
- if (NodeConfiguration.KeyStore.VerifyJwt(jwt, false))
- {
- //Validated
- }
- //May be signed by a cache server
- else if(NodeConfiguration.KeyStore.VerifyJwt(jwt, true))
- {
- //Set peer and verified flag since the another cache server signed the request
- isPeer = true;
- }
- else
- {
- Log.Information("Client signature verification failed");
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
- }
-
- //Recover json body
- using JsonDocument doc = jwt.GetPayload();
-
- if (doc.RootElement.TryGetProperty("sub", out JsonElement servIdEl))
- {
- nodeId = servIdEl.GetString();
- }
-
- if (doc.RootElement.TryGetProperty("chl", out JsonElement challengeEl))
- {
- challenge = challengeEl.GetString();
- }
+ Log.Information("Initial negotiation client signature verification failed");
+ return VirtualClose(entity, HttpStatusCode.Unauthorized);
}
- if (isPeer)
+ if (state.IsPeer)
{
- Log.Debug("Received negotiation request from peer node {node}", nodeId);
+ Log.Debug("Received negotiation request from peer node {node}", state.NodeId);
}
else
{
@@ -189,29 +149,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
}
//Verified, now we can create an auth message with a short expiration
- using JsonWebToken auth = new();
-
- auth.WriteHeader(NodeConfiguration.KeyStore.GetJwtHeader());
- auth.InitPayloadClaim()
- .AddClaim("aud", AudienceLocalServerId)
- .AddClaim("iat", entity.RequestedTimeUtc.ToUnixTimeSeconds())
- .AddClaim("exp", entity.RequestedTimeUtc.Add(AuthTokenExpiration).ToUnixTimeSeconds())
- .AddClaim("nonce", RandomHash.GetRandomBase32(8))
- .AddClaim("chl", challenge!)
- //Set the ispeer flag if the request was signed by a cache server
- .AddClaim("isPeer", isPeer)
- //Specify the server's node id if set
- .AddClaim("sub", nodeId!)
- //Set ip address
- .AddClaim("ip", entity.TrustedRemoteIp.ToString())
- //Add negotiaion args
- .AddClaim(FBMClient.REQ_HEAD_BUF_QUERY_ARG, CacheConfig.MaxHeaderBufferSize)
- .AddClaim(FBMClient.REQ_RECV_BUF_QUERY_ARG, CacheConfig.MaxRecvBufferSize)
- .AddClaim(FBMClient.REQ_MAX_MESS_QUERY_ARG, CacheConfig.MaxMessageSize)
- .CommitClaims();
-
- //Sign the auth message from our private key
- NodeConfiguration.KeyStore.SignJwt(auth);
+ using JsonWebToken auth = AuthManager.ConfirmCLientNegotiation(state, entity.TrustedRemoteIp, entity.RequestedTimeUtc);
//Close response
entity.CloseResponse(HttpStatusCode.OK, ContentType.Text, auth.DataBuffer);
@@ -222,101 +160,35 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
{
//Parse jwt from authorization
string? jwtAuth = entity.Server.Headers[HttpRequestHeader.Authorization];
-
- if (string.IsNullOrWhiteSpace(jwtAuth))
- {
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
- }
-
- //Get the upgrade signature header
string? clientSignature = entity.Server.Headers[FBMDataCacheExtensions.X_UPGRADE_SIG_HEADER];
+ string? optionalDiscovery = entity.Server.Headers[FBMDataCacheExtensions.X_NODE_DISCOVERY_HEADER];
- if (string.IsNullOrWhiteSpace(clientSignature))
+ //Not null
+ if (string.IsNullOrWhiteSpace(jwtAuth) || string.IsNullOrWhiteSpace(clientSignature))
{
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
+ return VfReturnType.Forbidden;
}
string? nodeId = null;
- CacheNodeAdvertisment? discoveryAd = null;
+ bool isPeer = false;
- //Parse jwt
- using (JsonWebToken jwt = JsonWebToken.Parse(jwtAuth))
+ //Validate upgrade request
+ if (!AuthManager.ValidateUpgrade(jwtAuth, clientSignature, entity.RequestedTimeUtc, entity.TrustedRemoteIp, ref nodeId, ref isPeer))
{
- //verify signature against the cache public key, since this server must have signed it
- if (!NodeConfiguration.KeyStore.VerifyCachePeer(jwt))
- {
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
- }
-
- //Recover json body
- using JsonDocument doc = jwt.GetPayload();
-
- //Verify audience, expiration
-
- if (!doc.RootElement.TryGetProperty("aud", out JsonElement audEl)
- || !AudienceLocalServerId.Equals(audEl.GetString(), StringComparison.OrdinalIgnoreCase))
- {
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
- }
-
- if (!doc.RootElement.TryGetProperty("exp", out JsonElement expEl)
- || DateTimeOffset.FromUnixTimeSeconds(expEl.GetInt64()) < entity.RequestedTimeUtc)
- {
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
- }
-
- //Check node ip address matches if required
- if (NodeConfiguration.VerifyIp)
- {
- if (!doc.RootElement.TryGetProperty("ip", out JsonElement ipEl))
- {
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
- }
-
- string? clientIp = ipEl.GetString();
- //Verify the client ip address matches the one in the token
- if (clientIp == null || !IPAddress.TryParse(clientIp, out IPAddress? clientIpAddr) || !clientIpAddr.Equals(entity.TrustedRemoteIp))
- {
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
- }
- }
-
- //Check if the client is a peer
- bool isPeer = doc.RootElement.TryGetProperty("isPeer", out JsonElement isPeerEl) && isPeerEl.GetBoolean();
-
- //The node id is optional and stored in the 'sub' field, ignore if the client is not a peer
- if (isPeer && doc.RootElement.TryGetProperty("sub", out JsonElement servIdEl))
- {
- nodeId = servIdEl.GetString();
- }
-
- //Verify the signature the client included of the auth token
+ return VirtualClose(entity, HttpStatusCode.Unauthorized);
+ }
- //Verify token signature against a fellow cache public key
- if (!NodeConfiguration.KeyStore.VerifyUpgradeToken(clientSignature, jwtAuth, isPeer))
- {
- entity.CloseResponse(HttpStatusCode.Unauthorized);
- return VfReturnType.VirtualSkip;
- }
+ CacheNodeAdvertisment? discoveryAd = null;
- if (isPeer)
- {
- //Try to get the node advertisement header
- string? discoveryHeader = entity.Server.Headers[FBMDataCacheExtensions.X_NODE_DISCOVERY_HEADER];
+ /*
+ * If the client is a peer server, it may offer a signed advertisment
+ * that this node will have the duty of making available to other peers
+ * if it is valid
+ */
- //Verify the node advertisement header and publish it
- if (!string.IsNullOrWhiteSpace(discoveryHeader))
- {
- discoveryAd = NodeConfiguration.KeyStore.VerifyPeerAdvertisment(discoveryHeader);
- }
- }
+ if (isPeer && !string.IsNullOrWhiteSpace(optionalDiscovery))
+ {
+ discoveryAd = NodeConfiguration.KeyStore.VerifyPeerAdvertisment(optionalDiscovery);
}
WsUserState state;
@@ -369,7 +241,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
Log.Debug("Client buffer state {state}", state);
//Accept socket and pass state object
- entity.AcceptWebSocket(WebsocketAcceptedAsync, state);
+ _ = entity.AcceptWebSocket(WebsocketAcceptedAsync, state);
return VfReturnType.VirtualSkip;
}
@@ -400,7 +272,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
};
//Check if the client is a peer node, if it is, subscribe to change events
- if (!string.IsNullOrWhiteSpace(state.NodeId))
+ if (state.IsPeer)
{
//Get the event queue for the current node
IPeerEventQueue queue = PubSubManager.Subscribe(state);
@@ -408,7 +280,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
try
{
//Begin listening for messages with a queue
- await Store.ListenAsync(wss, args, queue);
+ await Store.ListenAsync(wss, queue, args);
}
finally
{
@@ -419,7 +291,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
else
{
//Begin listening for messages without a queue
- await Store.ListenAsync(wss, args, null);
+ await Store.ListenAsync(wss, null!, args);
}
}
catch (OperationCanceledException)
@@ -456,6 +328,8 @@ namespace VNLib.Data.Caching.ObjectCache.Server.Endpoints
public string? NodeId { get; init; }
public CacheNodeAdvertisment? Advertisment { get; init; }
+ public bool IsPeer => !string.IsNullOrWhiteSpace(NodeId);
+
public override string ToString()
{
return
diff --git a/plugins/ObjectCacheServer/src/ICacheStore.cs b/plugins/ObjectCacheServer/src/ICacheStore.cs
index f911af9..a638169 100644
--- a/plugins/ObjectCacheServer/src/ICacheStore.cs
+++ b/plugins/ObjectCacheServer/src/ICacheStore.cs
@@ -38,7 +38,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server
/// <param name="state">The state parameter to pass to the data callback</param>
/// <param name="token">A token to cancel the async operation</param>
/// <returns>A value task that represents the async operation</returns>
- ValueTask AddOrUpdateBlobAsync<T>(string objectId, string? alternateId, GetBodyDataCallback<T> bodyData, T state, CancellationToken token = default);
+ ValueTask AddOrUpdateBlobAsync<T>(string objectId, string? alternateId, ObjectDataReader<T> bodyData, T state, CancellationToken token = default);
/// <summary>
/// Clears all items from the store
diff --git a/plugins/VNLib.Data.Caching.Providers.Redis/README.md b/plugins/VNLib.Data.Caching.Providers.Redis/README.md
new file mode 100644
index 0000000..f048e45
--- /dev/null
+++ b/plugins/VNLib.Data.Caching.Providers.Redis/README.md
@@ -0,0 +1,18 @@
+# VNLib.Data.Caching.Providers.Redis
+*A runtime asset library that exposes a Redis global cache client instance for application-wide caching*
+
+### Dependency notice
+This library depends on the StackExchange.Redis 3rd party library, all other dependencies are internal.
+
+## Builds
+Debug build w/ symbols & xml docs, release builds, NuGet packages, and individually packaged source code are available on my website (link below).
+
+## Docs and Guides
+Documentation, specifications, and setup guides are available on my website.
+
+[Docs and Articles](https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Data.Caching.Providers.Redis)
+[Builds and Source](https://www.vaughnnugent.com/resources/software/modules/VNLib.Data.Caching)
+[Nuget Feeds](https://www.vaughnnugent.com/resources/software/modules)
+
+## License
+Source files in for this project are licensed to you under the GNU Affero General Public License (or any later version). See the LICENSE files for more information. \ No newline at end of file
diff --git a/plugins/VNLib.Data.Caching.Providers.Redis/build.readme.md b/plugins/VNLib.Data.Caching.Providers.Redis/build.readme.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/VNLib.Data.Caching.Providers.Redis/build.readme.md
diff --git a/plugins/VNLib.Data.Caching.Providers.Redis/src/RedisClientCacheEntry.cs b/plugins/VNLib.Data.Caching.Providers.Redis/src/RedisClientCacheEntry.cs
new file mode 100644
index 0000000..30d936c
--- /dev/null
+++ b/plugins/VNLib.Data.Caching.Providers.Redis/src/RedisClientCacheEntry.cs
@@ -0,0 +1,380 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Providers.Redis
+* File: RedisClientCacheEntry.cs
+*
+* RedisClientCacheEntry.cs is part of VNLib.Data.Caching.Providers.Redis
+* which is part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Providers.Redis 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.Providers.Redis 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.Threading;
+using System.Threading.Tasks;
+
+using StackExchange.Redis;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Logging;
+using VNLib.Utils.Extensions;
+using VNLib.Plugins;
+using VNLib.Plugins.Extensions.Loading;
+
+namespace VNLib.Data.Caching.Providers.Redis
+{
+ /*
+ * This package exports an IGlobalCacheProvider that is intended to be packaged by
+ * application distributors that want to use Redis as a global cache for their
+ * application.
+ *
+ * The IGlobalCacheProvider primarily performs get/set operations on raw memory
+ * where possible. Custom serializers are allowed to be used for object serialziation.
+ *
+ * The interface also requires that implementations provide a fallback serialization
+ * method. For now, this is a JSON serializer. But will likely have more complex
+ * decision making where possible, such as protobufs
+ */
+
+ [ExternService]
+ [ConfigurationName("cache")]
+ public sealed class RedisClientCacheEntry : IGlobalCacheProvider
+ {
+ private const int InitialWriterBufferSize = 4096;
+
+ private readonly JsonCacheObjectSerializer _fallbackSerializer;
+ private readonly IUnmangedHeap _defaultHeap;
+ private readonly Task OnLoadTask;
+
+
+ private ConnectionMultiplexer? _redis;
+ private IDatabase? _database;
+
+ public RedisClientCacheEntry(PluginBase plugin, IConfigScope config)
+ {
+ _fallbackSerializer = new();
+ _defaultHeap = MemoryUtil.Shared;
+
+ ILogProvider redisLog = plugin.Log.CreateScope("REDIS");
+
+ //Allow a raw connection string to be used
+ if(config.ContainsKey("connection_string"))
+ {
+ string connectionString = config.GetRequiredProperty("connection_string", el => el.GetString()!);
+
+ //Store load task so it can be awaited by the host
+ OnLoadTask = Task.Run(async () =>
+ {
+
+ if(connectionString.Contains("password=[SECRET]", StringComparison.OrdinalIgnoreCase))
+ {
+ //Load the password from the secret store and replace the placeholder with the found secret
+ using ISecretResult password = await plugin.GetSecretAsync("redis_password");
+ connectionString = connectionString.Replace("password=[SECRET]", $"password={password.Result}", StringComparison.OrdinalIgnoreCase);
+ }
+
+ redisLog.Information("Connecting to Redis server...");
+
+ //Connect to the server
+ _redis = await ConnectionMultiplexer.ConnectAsync(connectionString);
+
+ _database = _redis.GetDatabase();
+
+ redisLog.Information("Successfully connected to Redis server");
+ });
+ }
+ else
+ {
+ ConfigurationOptions options = GetOptionsFromConfig(config);
+
+ //Store load task so it can be awaited by the host
+ OnLoadTask = Task.Run(async () =>
+ {
+ //Retrieve the password last
+ using ISecretResult password = await plugin.GetSecretAsync("redis_password");
+ options.Password = password.Result.ToString();
+
+ redisLog.Information("Connecting to Redis server...");
+
+ //Connect to the server
+ _redis = await ConnectionMultiplexer.ConnectAsync(options);
+
+ _database = _redis.GetDatabase();
+
+ redisLog.Information("Successfully connected to Redis server");
+ });
+ }
+ }
+
+ private static ConfigurationOptions GetOptionsFromConfig(IConfigScope config)
+ {
+ //Try go get the hostname
+ string? hostname = config.GetRequiredProperty("url", p => p.GetString()!);
+ Uri serverUri = new(hostname, UriKind.RelativeOrAbsolute);
+
+ ConfigurationOptions options = new()
+ {
+ Ssl = serverUri.Scheme == "rediss://",
+ };
+
+ //Add the host and port
+ options.EndPoints.Add(serverUri.DnsSafeHost, serverUri.Port);
+
+ //Get optional values
+ if (config.TryGetValue("user", out JsonElement user))
+ {
+ options.User = user.GetString();
+ }
+
+ if (config.TryGetValue("keepalive_sec", out JsonElement keepaliveSec))
+ {
+ options.KeepAlive = keepaliveSec.GetInt32();
+ }
+
+ if (config.TryGetValue("timeout_ms", out JsonElement timeoutMs))
+ {
+ options.SyncTimeout = timeoutMs.GetInt32();
+ }
+
+ if (config.TryGetValue("connect_timeout_ms", out JsonElement connectTimeoutMs))
+ {
+ options.ConnectTimeout = connectTimeoutMs.GetInt32();
+ }
+
+ if (config.TryGetValue("abort_on_connect_fail", out JsonElement abortOnConnectFail))
+ {
+ options.AbortOnConnectFail = abortOnConnectFail.GetBoolean();
+ }
+
+ if (config.TryGetValue("allow_admin", out JsonElement allowAdmin))
+ {
+ options.AllowAdmin = allowAdmin.GetBoolean();
+ }
+
+ if (config.TryGetValue("connect_retry", out JsonElement connectRetry))
+ {
+ options.ConnectRetry = connectRetry.GetInt32();
+ }
+
+ if (config.TryGetValue("connect_timeout", out JsonElement connectTimeout))
+ {
+ options.ConnectTimeout = connectTimeout.GetInt32();
+ }
+
+ if (config.TryGetValue("default_database", out JsonElement defaultDatabase))
+ {
+ options.DefaultDatabase = defaultDatabase.GetInt32();
+ }
+
+ if (config.TryGetValue("keep_alive", out JsonElement keepAlive))
+ {
+ options.KeepAlive = keepAlive.GetInt32();
+ }
+
+ if (config.TryGetValue("name", out JsonElement name))
+ {
+ options.ClientName = name.GetString();
+ }
+
+ return options;
+ }
+
+ ///<inheritdoc/>
+ public bool IsConnected => _redis?.IsConnected == true;
+
+ //Called by the host to wait for the cache to be loaded
+ public Task InitAsync() => OnLoadTask;
+
+ ///<inheritdoc/>
+ public Task AddOrUpdateAsync<T>(string key, string? newKey, T value, CancellationToken cancellation) => AddOrUpdateAsync(key, newKey, value, _fallbackSerializer, cancellation);
+
+ ///<inheritdoc/>
+ public async Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation)
+ {
+ _ = key ?? throw new ArgumentNullException(nameof(key));
+ _ = serialzer ?? throw new ArgumentNullException(nameof(serialzer));
+
+ //Alloc update buffer
+ using AddOrUpdateBuffer buffer = new(_defaultHeap, InitialWriterBufferSize, false);
+
+ //Serialize the object
+ serialzer.Serialize(value, buffer);
+
+ //Update object data
+ await _database.StringSetAsync(key, buffer.GetWrittenData());
+
+ if (!string.IsNullOrWhiteSpace(newKey))
+ {
+ //also update the key
+ await _database.KeyRenameAsync(key, newKey);
+ }
+ }
+
+ ///<inheritdoc/>
+ public async Task AddOrUpdateAsync<T>(string key, string? newKey, ObjectDataReader<T> callback, T state, CancellationToken cancellation)
+ {
+ /*
+ * Because the redis database only allows ReadonlyMemory when
+ * updating keys, we must copy the object data into a temporary
+ * heap buffer and then copy it into the database.
+ */
+
+ int length = 0;
+
+ //Create a copy buffer and copy the object data into it
+ using IMemoryOwner<byte> buffer = AllocAndCopy(callback, state, _defaultHeap, ref length);
+
+ //Set the value at the old key
+ await _database.StringSetAsync(key, buffer.Memory[..length]);
+
+ //If required also update the key
+ if (!string.IsNullOrWhiteSpace(newKey))
+ {
+ await _database.KeyRenameAsync(key, newKey);
+ }
+
+ static IMemoryOwner<byte> AllocAndCopy(ObjectDataReader<T> callback, T state, IUnmangedHeap heap, ref int length)
+ {
+ //Get the buffer from the callback
+ ReadOnlySpan<byte> data = callback(state);
+ length = data.Length;
+
+ //Alloc the buffer on the desired heap
+ MemoryManager<byte> buffer = heap.DirectAlloc<byte>(length, false);
+
+ //Copy object data to the buffer
+ data.CopyTo(buffer.GetSpan());
+
+ return buffer;
+ }
+ }
+
+ ///<inheritdoc/>
+ public async Task<bool> DeleteAsync(string key, CancellationToken cancellation)
+ {
+ RedisValue value = await _database.StringGetDeleteAsync(key);
+ return value.IsNull == false; //Should only be null if the key did not exist
+ }
+
+ ///<inheritdoc/>
+ public Task<T?> GetAsync<T>(string key, CancellationToken cancellation) => GetAsync<T>(key, _fallbackSerializer, cancellation);
+
+ ///<inheritdoc/>
+ public async Task<T?> GetAsync<T>(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation)
+ {
+ _ = deserializer ?? throw new ArgumentNullException(nameof(deserializer));
+
+ //Try to get the value from the cache
+ RedisValue value = await _database.StringGetAsync(key);
+
+ //If the value is found, set the raw data
+ if (value.IsNull)
+ {
+ return default;
+ }
+
+ return deserializer.Deserialize<T>(((ReadOnlyMemory<byte>)value).Span);
+ }
+
+ ///<inheritdoc/>
+ public async Task GetAsync<T>(string key, ObjectDataSet<T> callback, T state, CancellationToken cancellation)
+ {
+ _ = callback ?? throw new ArgumentNullException(nameof(callback));
+
+ //Try to get the value from the cache
+ RedisValue value = await _database.StringGetAsync(key);
+
+ //If the value is found, set the raw data
+ if (!value.IsNull)
+ {
+ //Invoke callback with object data
+ callback(state, ((ReadOnlyMemory<byte>)value).Span);
+ }
+ }
+
+ ///<inheritdoc/>
+ public object GetUnderlyingStore()
+ {
+ return _database == null ? throw new InvalidOperationException("The cache store is not available") : _database;
+ }
+
+ private sealed class AddOrUpdateBuffer: VnDisposeable, IBufferWriter<byte>
+ {
+ private readonly MemoryHandle<byte> _handle;
+ private readonly MemoryManager<byte> _manager;
+
+ private int _position;
+
+ public AddOrUpdateBuffer(IUnmangedHeap heap, int initialSize, bool zero)
+ {
+ _handle = heap.Alloc<byte>(CalNewSize(initialSize), zero);
+ //Create memory manager around the memhandle that does not own the handle
+ _manager = _handle.ToMemoryManager(false);
+ }
+
+ public void Advance(int count)
+ {
+ if(count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ _position += count;
+ }
+
+ ///<inheritdoc/>
+ public Memory<byte> GetMemory(int sizeHint = 0)
+ {
+ nint newSize = CalNewSize(sizeHint);
+
+ //Resize if needed
+ _handle.ResizeIfSmaller(newSize);
+
+ //Return the memory
+ return _manager.Memory.Slice(_position, sizeHint);
+ }
+
+ nint CalNewSize(int size) => MemoryUtil.NearestPage(size + _position);
+
+ ///<inheritdoc/>
+ public Span<byte> GetSpan(int sizeHint = 0)
+ {
+ nint newSize = CalNewSize(sizeHint);
+
+ //Resize if needed
+ _handle.ResizeIfSmaller(newSize);
+
+ //Return the memory
+ return _handle.AsSpan(_position);
+ }
+
+ /// <summary>
+ /// Gets the written data
+ /// </summary>
+ /// <returns>The memory segment pointing to the data that was written by the serializer</returns>
+ public ReadOnlyMemory<byte> GetWrittenData() => _manager.Memory[.._position];
+
+ protected override void Free()
+ {
+ //Free the handle, dont need to free memory manager
+ _handle.Dispose();
+ }
+ }
+ }
+}
diff --git a/plugins/VNLib.Data.Caching.Providers.Redis/src/VNLib.Data.Caching.Providers.Redis.csproj b/plugins/VNLib.Data.Caching.Providers.Redis/src/VNLib.Data.Caching.Providers.Redis.csproj
new file mode 100644
index 0000000..70fab74
--- /dev/null
+++ b/plugins/VNLib.Data.Caching.Providers.Redis/src/VNLib.Data.Caching.Providers.Redis.csproj
@@ -0,0 +1,54 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <RootNamespace>VNLib.Data.Caching.Providers.Redis</RootNamespace>
+ <AssemblyName>VNLib.Data.Caching.Providers.Redis</AssemblyName>
+ <Nullable>enable</Nullable>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <ProduceReferenceAssembly>True</ProduceReferenceAssembly>
+ <GenerateDocumentationFile>False</GenerateDocumentationFile>
+ <!-- Resolve nuget dll files and store them in the output dir -->
+ <EnableDynamicLoading>true</EnableDynamicLoading>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Data.Caching.Providers.Redis</Product>
+ <PackageId>VNLib.Data.Caching.Providers.Redis</PackageId>
+ <Description>A runtime asset library that exposes a Redis global cache client instance for application-wide caching</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/plugins/VNLib.Data.Caching.Providers.Redis</RepositoryUrl>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <PackageLicenseFile>LICENSE</PackageLicenseFile>
+ <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <None Include="..\..\..\LICENSE">
+ <Pack>True</Pack>
+ <PackagePath>\</PackagePath>
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ <None Include="..\README.md">
+ <Pack>True</Pack>
+ <PackagePath>\</PackagePath>
+ </None>
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="StackExchange.Redis" Version="2.7.4" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Loading\src\VNLib.Plugins.Extensions.Loading.csproj" />
+ <ProjectReference Include="..\..\..\lib\VNLib.Data.Caching\src\VNLib.Data.Caching.csproj" />
+ </ItemGroup>
+
+ <Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">
+ <Exec Command="start xcopy &quot;$(TargetDir)&quot; &quot;..\..\..\..\..\devplugins\runtimeassets\$(TargetName)&quot; /E /Y /R" />
+ </Target>
+
+</Project>
diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/README.md b/plugins/VNLib.Data.Caching.Providers.VNCache/README.md
new file mode 100644
index 0000000..3642cbd
--- /dev/null
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/README.md
@@ -0,0 +1,18 @@
+# VNLib.Data.Caching.Providers.VNCache
+*A runtime asset VNCache client library that exposes an IGlobalCacheProvider that works with VNCache clusters, write through memory-cache, and memory only cache*
+
+### Dependency notice
+This library depends on the VNLib.Net.Rest.Client library which requires RestSharp.
+
+## Builds
+Debug build w/ symbols & xml docs, release builds, NuGet packages, and individually packaged source code are available on my website (link below).
+
+## Docs and Guides
+Documentation, specifications, and setup guides are available on my website.
+
+[Docs and Articles](https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Data.Caching.Providers.VNCache)
+[Builds and Source](https://www.vaughnnugent.com/resources/software/modules/VNLib.Data.Caching)
+[Nuget Feeds](https://www.vaughnnugent.com/resources/software/modules)
+
+## License
+Source files in for this project are licensed to you under the GNU Affero General Public License (or any later version). See the LICENSE files for more information. \ No newline at end of file
diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/build.readme.md b/plugins/VNLib.Data.Caching.Providers.VNCache/build.readme.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/build.readme.md
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/AddOrUpdateBuffer.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/AddOrUpdateBuffer.cs
index a1fe2b5..cf3cf63 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/AddOrUpdateBuffer.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/AddOrUpdateBuffer.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
+* Package: VNLib.Data.Caching.Providers.VNCache
* File: AddOrUpdateBuffer.cs
*
-* AddOrUpdateBuffer.cs is part of VNLib.Plugins.Extensions.VNCache
+* AddOrUpdateBuffer.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -28,16 +28,15 @@ using System.Buffers;
using VNLib.Utils;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
-using VNLib.Data.Caching;
-namespace VNLib.Plugins.Extensions.VNCache
+namespace VNLib.Data.Caching.Providers.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
+ internal sealed class AddOrUpdateBuffer : VnDisposeable, IBufferWriter<byte>, IObjectData
{
private int _count;
private readonly IUnmangedHeap _heap;
@@ -48,18 +47,21 @@ namespace VNLib.Plugins.Extensions.VNCache
_heap = heap;
}
+ ///<inheritdoc/>
public void Advance(int count)
{
//Update count
_count += count;
}
- public Memory<byte> GetMemory(int sizeHint = 0)
+ ///<inheritdoc/>
+ Memory<byte> IBufferWriter<byte>.GetMemory(int sizeHint = 0)
{
throw new NotImplementedException();
}
- public Span<byte> GetSpan(int sizeHint = 0)
+ ///<inheritdoc/>
+ Span<byte> IBufferWriter<byte>.GetSpan(int sizeHint = 0)
{
//Round to nearest page for new size
nint newSize = MemoryUtil.NearestPage(sizeHint + _count);
@@ -78,16 +80,13 @@ namespace VNLib.Plugins.Extensions.VNCache
return _buffer.AsSpan(_count);
}
- public void SetData(ReadOnlySpan<byte> data)
+ void IObjectData.SetData(ReadOnlySpan<byte> data)
{
throw new NotSupportedException();
}
- public ReadOnlySpan<byte> GetData()
- {
- //Get stored data from within handle
- return _buffer!.AsSpan(0, _count);
- }
+ ///<inheritdoc/>
+ public ReadOnlySpan<byte> GetData() => _buffer!.AsSpan(0, _count);
protected override void Free()
{
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/BucketLocalManagerFactory.cs
index cea06a3..0f49849 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/BucketLocalManagerFactory.cs
@@ -32,9 +32,10 @@ using VNLib.Plugins;
using VNLib.Utils;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
+using VNLib.Data.Caching.ObjectCache;
using VNLib.Plugins.Extensions.Loading;
-namespace VNLib.Data.Caching.ObjectCache.Server
+namespace VNLib.Data.Caching.Providers.VNCache
{
[ConfigurationName("memory_manager", Required = false)]
internal sealed class BucketLocalManagerFactory : VnDisposeable, ICacheMemoryManagerFactory
@@ -60,10 +61,10 @@ namespace VNLib.Data.Caching.ObjectCache.Server
/// </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);
+ public static BucketLocalManagerFactory Create(bool zeroAll) => new(zeroAll);
private BucketLocalManagerFactory(bool zeroAll)
- {
+ {
_zeroAll = zeroAll;
}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/ClusterNodeIndex.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs
index 487a4f9..a6e264c 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/ClusterNodeIndex.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
+* Package: VNLib.Data.Caching.Providers.VNCache
* File: ClusterNodeIndex.cs
*
-* ClusterNodeIndex.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger
+* ClusterNodeIndex.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -30,7 +30,7 @@ using VNLib.Data.Caching.Extensions;
using VNLib.Data.Caching.Extensions.Clustering;
using VNLib.Plugins.Extensions.Loading.Events;
-namespace VNLib.Plugins.Extensions.VNCache.Clustering
+namespace VNLib.Data.Caching.Providers.VNCache.Clustering
{
internal sealed class ClusterNodeIndex : IClusterNodeIndex, IIntervalScheduleable
{
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/IClusterNodeIndex.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/IClusterNodeIndex.cs
index ffbfa0d..285f405 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/Clustering/IClusterNodeIndex.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/IClusterNodeIndex.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
+* Package: VNLib.Data.Caching.Providers.VNCache
* File: IClusterNodeIndex.cs
*
-* IClusterNodeIndex.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger
+* IClusterNodeIndex.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -27,7 +27,7 @@ using System.Threading.Tasks;
using VNLib.Data.Caching.Extensions.Clustering;
-namespace VNLib.Plugins.Extensions.VNCache.Clustering
+namespace VNLib.Data.Caching.Providers.VNCache.Clustering
{
internal interface IClusterNodeIndex
{
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/FBMCacheClient.cs
index e2d0176..f952bcb 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/FBMCacheClient.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
-* File: VnCacheClient.cs
+* Package: VNLib.Data.Caching.Providers.VNCache
+* File: FBMCacheClient.cs
*
-* VnCacheClient.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger
+* FBMCacheClient.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -35,29 +35,23 @@ 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;
using VNLib.Data.Caching.Extensions.Clustering;
+using VNLib.Plugins;
+using VNLib.Plugins.Extensions.Loading;
using VNLib.Plugins.Extensions.Loading.Events;
-using VNLib.Plugins.Extensions.VNCache.Clustering;
-namespace VNLib.Plugins.Extensions.VNCache
-{
- public interface ICacheRefreshPolicy
- {
- TimeSpan MaxCacheAge { get; }
+using VNLib.Data.Caching.Providers.VNCache.Clustering;
- TimeSpan RefreshInterval { get; }
- }
+namespace VNLib.Data.Caching.Providers.VNCache
+{
/// <summary>
/// A base class that manages
/// </summary>
- [ConfigurationName(VNCacheExtensions.CACHE_CONFIG_KEY)]
- internal class VnCacheClient : IGlobalCacheProvider, IAsyncBackgroundWork
+ [ConfigurationName(VNCacheClient.CACHE_CONFIG_KEY)]
+ internal class FBMCacheClient : IGlobalCacheProvider, IAsyncBackgroundWork
{
private const string LOG_NAME = "CLIENT";
private static readonly TimeSpan InitialDelay = TimeSpan.FromSeconds(10);
@@ -76,8 +70,8 @@ namespace VNLib.Plugins.Extensions.VNCache
/// </summary>
public bool IsConnected { get; private set; }
- public VnCacheClient(PluginBase plugin, IConfigScope config)
- :this(
+ public FBMCacheClient(PluginBase plugin, IConfigScope config)
+ : this(
config.Deserialze<VnCacheClientConfig>(),
plugin.IsDebug() ? plugin.Log : null
)
@@ -93,7 +87,7 @@ namespace VNLib.Plugins.Extensions.VNCache
plugin.ScheduleInterval(_index, _config.DiscoveryInterval);
//Run discovery after initial delay if interval is greater than initial delay
- if(_config.DiscoveryInterval > InitialDelay)
+ if (_config.DiscoveryInterval > InitialDelay)
{
//Run a manual initial load
scoped.Information("Running initial discovery in {delay}", InitialDelay);
@@ -101,7 +95,7 @@ namespace VNLib.Plugins.Extensions.VNCache
}
}
- public VnCacheClient(VnCacheClientConfig config, ILogProvider? debugLog)
+ public FBMCacheClient(VnCacheClientConfig config, ILogProvider? debugLog)
{
//Validate config
(config as IOnConfigValidation).Validate();
@@ -132,7 +126,7 @@ namespace VNLib.Plugins.Extensions.VNCache
pluginLog = pluginLog.CreateScope(LOG_NAME);
try
- {
+ {
//Initial delay
pluginLog.Debug("Worker started, waiting for startup delay");
await Task.Delay((int)InitialDelay.TotalMilliseconds + 1000, exitToken);
@@ -144,7 +138,7 @@ namespace VNLib.Plugins.Extensions.VNCache
//Wait for a discovery to complete
await _index.WaitForDiscoveryAsync(exitToken);
}
- catch(CacheDiscoveryFailureException cdfe)
+ catch (CacheDiscoveryFailureException cdfe)
{
pluginLog.Error("Failed to discover nodes, will try again\n{err}", cdfe.Message);
//Continue
@@ -162,7 +156,7 @@ namespace VNLib.Plugins.Extensions.VNCache
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);
}
@@ -196,7 +190,7 @@ namespace VNLib.Plugins.Extensions.VNCache
pluginLog.Information("Cache server disconnected");
}
- catch(TimeoutException)
+ catch (TimeoutException)
{
pluginLog.Warn("Failed to establish a websocket connection to cache server within the timeout period");
}
@@ -211,7 +205,7 @@ namespace VNLib.Plugins.Extensions.VNCache
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)
+ catch (HttpRequestException he) when (he.StatusCode.HasValue)
{
pluginLog.Warn("Failed to negotiate with cache server {reason}", he.Message);
pluginLog.Verbose("Stack trace: {re}", he);
@@ -255,7 +249,7 @@ namespace VNLib.Plugins.Extensions.VNCache
}
///<inheritdoc/>
- public virtual Task DeleteAsync(string key, CancellationToken cancellation)
+ public virtual Task<bool> DeleteAsync(string key, CancellationToken cancellation)
{
return !IsConnected
? throw new InvalidOperationException("The underlying client is not connected to a cache node")
@@ -271,7 +265,7 @@ namespace VNLib.Plugins.Extensions.VNCache
}
///<inheritdoc/>
- public virtual Task<T?> GetAsync<T>(string key, ICacheObjectDeserialzer deserializer, CancellationToken cancellation)
+ public virtual Task<T?> GetAsync<T>(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation)
{
return !IsConnected
? throw new InvalidOperationException("The underlying client is not connected to a cache node")
@@ -279,7 +273,7 @@ namespace VNLib.Plugins.Extensions.VNCache
}
///<inheritdoc/>
- public virtual Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation)
+ public virtual Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation)
{
return !IsConnected
? throw new InvalidOperationException("The underlying client is not connected to a cache node")
@@ -287,21 +281,24 @@ namespace VNLib.Plugins.Extensions.VNCache
}
///<inheritdoc/>
- public virtual Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation)
+ public virtual Task GetAsync<T>(string key, ObjectDataSet<T> callback, T state, CancellationToken cancellation)
{
return !IsConnected
- ? throw new InvalidOperationException("The underlying client is not connected to a cache node")
- : Client!.GetObjectAsync(key, rawData, cancellation);
+ ? throw new InvalidOperationException("The underlying client is not connected to a cache node")
+ : Client!.GetObjectAsync(key, callback, state, cancellation);
}
///<inheritdoc/>
- public virtual Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation)
+ public virtual Task AddOrUpdateAsync<T>(string key, string? newKey, ObjectDataReader<T> callback, T state, CancellationToken cancellation)
{
return !IsConnected
- ? throw new InvalidOperationException("The underlying client is not connected to a cache node")
- : Client!.AddOrUpdateObjectAsync(key, newKey, rawData, cancellation);
+ ? throw new InvalidOperationException("The underlying client is not connected to a cache node")
+ : Client!.AddOrUpdateObjectAsync(key, newKey, callback, state, cancellation);
}
+ ///<inheritdoc/>
+ public object GetUnderlyingStore() => Client; //Client is the underlying "store"
+
private sealed class AuthManager : ICacheAuthManager
{
@@ -323,7 +320,7 @@ namespace VNLib.Plugins.Extensions.VNCache
{
await _sigKey;
await _verKey;
- }
+ }
///<inheritdoc/>
public IReadOnlyDictionary<string, string?> GetJwtHeader()
@@ -344,14 +341,14 @@ namespace VNLib.Plugins.Extensions.VNCache
{
//try to get the rsa alg for the signing key
using RSA? rsa = _sigKey.Value.GetRSAPrivateKey();
- if(rsa != null)
+ 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)
+ if (ecdsa != null)
{
return ecdsa.SignHash(hash);
}
@@ -381,7 +378,7 @@ namespace VNLib.Plugins.Extensions.VNCache
{
return ecdsa.VerifyHash(hash, signature);
}
-
+
throw new NotSupportedException("The current key is not an RSA or ECDSA key and is not supported");
}
}
diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/ICacheRefreshPolicy.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/ICacheRefreshPolicy.cs
new file mode 100644
index 0000000..a37465d
--- /dev/null
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/ICacheRefreshPolicy.cs
@@ -0,0 +1,35 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Providers.VNCache
+* File: ICacheRefreshPolicy.cs
+*
+* ICacheRefreshPolicy.cs is part of VNLib.Data.Caching.Providers.VNCache which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Providers.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.Data.Caching.Providers.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;
+
+namespace VNLib.Data.Caching.Providers.VNCache
+{
+ public interface ICacheRefreshPolicy
+ {
+ TimeSpan MaxCacheAge { get; }
+
+ TimeSpan RefreshInterval { get; }
+ }
+} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCache.cs
index a56529b..7d03918 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCache.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
+* Package: VNLib.Data.Caching.Providers.VNCache
* File: MemoryCache.cs
*
-* MemoryCache.cs is part of VNLib.Plugins.Extensions.VNCache
+* MemoryCache.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -33,12 +33,12 @@ using VNLib.Utils.Logging;
using VNLib.Utils.Memory.Diagnostics;
using VNLib.Data.Caching;
using VNLib.Data.Caching.ObjectCache;
+using VNLib.Plugins;
using VNLib.Plugins.Extensions.Loading;
-using VNLib.Data.Caching.ObjectCache.Server;
-namespace VNLib.Plugins.Extensions.VNCache
+namespace VNLib.Data.Caching.Providers.VNCache
{
- [ConfigurationName(VNCacheExtensions.CACHE_CONFIG_KEY)]
+ [ConfigurationName(VNCacheClient.CACHE_CONFIG_KEY)]
internal sealed class MemoryCache : VnDisposeable, IGlobalCacheProvider
{
const int MB_DIVISOR = 1000 * 1024;
@@ -56,24 +56,25 @@ namespace VNLib.Plugins.Extensions.VNCache
| -----------------------------
";
- private readonly ICacheObjectSerialzer _serialzer;
- private readonly ICacheObjectDeserialzer _deserialzer;
+ private readonly ICacheObjectSerializer _serialzer;
+ private readonly ICacheObjectDeserializer _deserialzer;
private readonly IBlobCacheTable _memCache;
private readonly IUnmangedHeap _bufferHeap;
- private readonly BucketLocalManagerFactory _blobCacheMemManager;
+ private readonly BucketLocalManagerFactory? _blobCacheMemManager;
public MemoryCache(PluginBase pbase, IConfigScope config)
- :this(
- config[VNCacheExtensions.MEMORY_CACHE_CONFIG_KEY].Deserialize<MemoryCacheConfig>()!,
- pbase.IsDebug(),
- pbase.Log
+ : this(
+ config[VNCacheClient.MEMORY_CACHE_CONFIG_KEY].Deserialize<MemoryCacheConfig>()!,
+ pbase.IsDebug(),
+ pbase.Log,
+ pbase.GetOrCreateSingleton<BucketLocalManagerFactory>()
)
{ }
- public MemoryCache(MemoryCacheConfig config):this(config, false, null)
+ public MemoryCache(MemoryCacheConfig config) : this(config, false, null, null)
{ }
- private MemoryCache(MemoryCacheConfig config, bool isDebug, ILogProvider? log)
+ private MemoryCache(MemoryCacheConfig config, bool isDebug, ILogProvider? log, BucketLocalManagerFactory? factory)
{
//Validate config
config.Validate();
@@ -92,10 +93,11 @@ namespace VNLib.Plugins.Extensions.VNCache
_bufferHeap = MemoryUtil.InitializeNewHeapForProcess();
}
- _blobCacheMemManager = BucketLocalManagerFactory.Create(config.ZeroAllAllocations);
+ //Fallback to creating a local/single instance of the manager
+ factory ??= _blobCacheMemManager = BucketLocalManagerFactory.Create(config.ZeroAllAllocations);
//Setup cache table
- _memCache = new BlobCacheTable(config.TableSize, config.BucketSize, _blobCacheMemManager, null);
+ _memCache = new BlobCacheTable(config.TableSize, config.BucketSize, factory, null);
/*
* Default to json serialization by using the default
@@ -113,9 +115,9 @@ namespace VNLib.Plugins.Extensions.VNCache
{
long maxObjects = config.BucketSize * config.TableSize;
- long size4kMb = (maxObjects * 4096)/MB_DIVISOR;
- long size8kMb = (maxObjects * 8128)/MB_DIVISOR;
- long size16kMb = (maxObjects * 16384)/MB_DIVISOR;
+ 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);
}
@@ -127,14 +129,14 @@ namespace VNLib.Plugins.Extensions.VNCache
{
_memCache.Dispose();
_bufferHeap.Dispose();
- _blobCacheMemManager.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)
+ public async Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation)
{
Check();
@@ -149,7 +151,7 @@ namespace VNLib.Plugins.Extensions.VNCache
}
///<inheritdoc/>
- public Task DeleteAsync(string key, CancellationToken cancellation)
+ public Task<bool> DeleteAsync(string key, CancellationToken cancellation)
{
Check();
return _memCache.DeleteObjectAsync(key, cancellation).AsTask();
@@ -159,7 +161,7 @@ namespace VNLib.Plugins.Extensions.VNCache
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)
+ public async Task<T?> GetAsync<T>(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation)
{
Check();
@@ -173,7 +175,7 @@ namespace VNLib.Plugins.Extensions.VNCache
//Try to read the value
if (cache.TryGetValue(key, out CacheEntry entry))
{
- return deserializer.Deserialze<T>(entry.GetDataSegment());
+ return deserializer.Deserialize<T>(entry.GetDataSegment());
}
return default;
@@ -185,7 +187,7 @@ namespace VNLib.Plugins.Extensions.VNCache
}
///<inheritdoc/>
- public async Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation)
+ public async Task GetAsync<T>(string key, ObjectDataSet<T> callback, T state, CancellationToken cancellation)
{
Check();
@@ -201,7 +203,7 @@ namespace VNLib.Plugins.Extensions.VNCache
if (cache.TryGetValue(key, out CacheEntry entry))
{
//Set result data
- rawData.SetData(entry.GetDataSegment());
+ callback(state, entry.GetDataSegment());
}
}
finally
@@ -211,12 +213,16 @@ namespace VNLib.Plugins.Extensions.VNCache
}
///<inheritdoc/>
- public Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation)
+ public Task AddOrUpdateAsync<T>(string key, string? newKey, ObjectDataReader<T> callback, T state, CancellationToken cancellation)
{
Check();
//Update object data
- return _memCache.AddOrUpdateObjectAsync(key, newKey, static b => b.GetData(), rawData, default, cancellation).AsTask();
+ return _memCache.AddOrUpdateObjectAsync(key, newKey, callback, state, default, cancellation).AsTask();
}
+
+ ///<inheritdoc/>
+ public object GetUnderlyingStore() => _memCache;
+
}
} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCacheConfig.cs
index 57c2793..176333f 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCacheConfig.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
+* Package: VNLib.Data.Caching.Providers.VNCache
* File: MemoryCacheConfig.cs
*
-* MemoryCacheConfig.cs is part of VNLib.Plugins.Extensions.VNCache
+* MemoryCacheConfig.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -27,7 +27,7 @@ using System.Text.Json.Serialization;
using VNLib.Plugins.Extensions.Loading;
-namespace VNLib.Plugins.Extensions.VNCache
+namespace VNLib.Data.Caching.Providers.VNCache
{
/// <summary>
/// Memorycache configuration object. Json-(de)serializable
@@ -50,7 +50,7 @@ namespace VNLib.Plugins.Extensions.VNCache
/// The maxium size (in bytes) of each cache entry within any bucket
/// </summary>
[JsonPropertyName("max_object_size")]
- public uint MaxBlobSize { get; set; } = 16 * 1024;
+ public uint MaxBlobSize { get; set; } = 16 * 1024;
[JsonIgnore]
public TimeSpan MaxCacheAge { get; set; } = TimeSpan.FromMinutes(1);
@@ -90,17 +90,17 @@ namespace VNLib.Plugins.Extensions.VNCache
///<inheritdoc/>
public void Validate()
{
- if(TableSize == 0)
+ if (TableSize == 0)
{
throw new ArgumentException("You must specify a cache bucket table size", "buckets");
}
- if(BucketSize == 0)
+ if (BucketSize == 0)
{
throw new ArgumentException("You must specify the maxium number of entires allowed in each bucket ", "bucket_size");
}
- if(MaxBlobSize < 16)
+ if (MaxBlobSize < 16)
{
throw new ArgumentException("You must configure a maximum object size", "max_object_size");
}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheOperator.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCacheOperator.cs
index b5b1234..739ab71 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheOperator.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/MemoryCacheOperator.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
+* Package: VNLib.Data.Caching.Providers.VNCache
* File: MemoryCacheOperator.cs
*
-* MemoryCacheOperator.cs is part of VNLib.Plugins.Extensions.VNCache which is
+* MemoryCacheOperator.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -22,10 +22,10 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-using VNLib.Data.Caching;
using VNLib.Utils;
+using VNLib.Plugins;
-namespace VNLib.Plugins.Extensions.VNCache
+namespace VNLib.Data.Caching.Providers.VNCache
{
/// <summary>
/// A disposable memory cache operator handle. When cache use is complete, you should
diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteBackedMemoryCache.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteBackedMemoryCache.cs
new file mode 100644
index 0000000..c14ddb9
--- /dev/null
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteBackedMemoryCache.cs
@@ -0,0 +1,350 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.Providers.VNCache
+* File: RemoteBackedMemoryCache.cs
+*
+* RemoteBackedMemoryCache.cs is part of VNLib.Data.Caching.Providers.VNCache
+* which is part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.Providers.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.Data.Caching.Providers.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;
+using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Loading.Events;
+
+namespace VNLib.Data.Caching.Providers.VNCache
+{
+
+ /*
+ * A combined 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(VNCacheClient.CACHE_CONFIG_KEY)]
+ internal sealed class RemoteBackedMemoryCache : IDisposable, IGlobalCacheProvider, IIntervalScheduleable
+ {
+ private readonly MemoryCacheConfig _cacheConfig;
+ private readonly ICacheObjectSerializer _fallbackSerializer;
+ private readonly ICacheObjectDeserializer _fallbackDeserializer;
+ private readonly IBlobCacheTable _memCache;
+ private readonly IGlobalCacheProvider _backing;
+ private readonly IUnmangedHeap _bufferHeap;
+ private readonly BucketLocalManagerFactory? _bucketFactory;
+
+ public RemoteBackedMemoryCache(PluginBase plugin, IConfigScope config)
+ : this(
+ config.GetRequiredProperty(VNCacheClient.MEMORY_CACHE_CONFIG_KEY, p => p.Deserialize<MemoryCacheConfig>()!),
+ plugin.GetOrCreateSingleton<FBMCacheClient>(), //Cache client is backing store
+ plugin.GetOrCreateSingleton<BucketLocalManagerFactory>()
+ )
+ {
+
+ //Schedule cache purge interval
+ if (_cacheConfig.RefreshInterval > TimeSpan.Zero)
+ {
+ plugin.ScheduleInterval(this, _cacheConfig.RefreshInterval);
+ }
+ }
+
+
+ public RemoteBackedMemoryCache(MemoryCacheConfig memCache, IGlobalCacheProvider backingStore) : this(memCache, backingStore, null)
+ { }
+
+ public RemoteBackedMemoryCache(MemoryCacheConfig memCache, IGlobalCacheProvider backingStore, BucketLocalManagerFactory? factory)
+ {
+ _ = memCache ?? throw new ArgumentNullException(nameof(memCache));
+ _ = backingStore ?? throw new ArgumentNullException(nameof(backingStore));
+
+ memCache.Validate();
+
+ /*
+ * If no buffer factory was supplied, we can create one, but it has to be
+ * disposed manually on exit. If one was supplied, we can use it but we do not
+ * manage it's lifetime
+ */
+
+ factory ??= _bucketFactory = BucketLocalManagerFactory.Create(memCache.ZeroAllAllocations);
+
+ //Setup mem cache table
+ _memCache = new BlobCacheTable(memCache.TableSize, memCache.BucketSize, factory, null);
+
+ //If backing store is a VnCacheClient, steal it's buffer heap
+ _bufferHeap = backingStore is FBMCacheClient client && client.Client.Config.MemoryManager.TryGetHeap(out IUnmangedHeap? heap) ? heap : MemoryUtil.Shared;
+
+
+ _cacheConfig = memCache;
+ _backing = backingStore;
+
+ /*
+ * Default to json serialization by using the default
+ * serializer and JSON options
+ */
+
+ JsonCacheObjectSerializer defaultSerializer = new();
+ _fallbackSerializer = defaultSerializer;
+ _fallbackDeserializer = defaultSerializer;
+ }
+
+ void IDisposable.Dispose()
+ {
+ //Dispose of the memory cache
+ _memCache.Dispose();
+ _bucketFactory?.Dispose();
+
+ if (_backing is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+ }
+
+ ///<inheritdoc/>
+ object IGlobalCacheProvider.GetUnderlyingStore() => _backing.GetUnderlyingStore();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void CheckConnected()
+ {
+ if (!_backing.IsConnected)
+ {
+ throw new InvalidOperationException("The client is not connected to the remote cache");
+ }
+ }
+
+ ///<inheritdoc/>
+ public bool IsConnected => _backing.IsConnected;
+
+ ///<inheritdoc/>
+ public Task AddOrUpdateAsync<T>(string key, string? newKey, T value, CancellationToken cancellation)
+ => AddOrUpdateAsync(key, newKey, value, _fallbackSerializer, cancellation);
+
+ ///<inheritdoc/>
+ public Task<bool> DeleteAsync(string key, CancellationToken cancellation)
+ {
+ CheckConnected();
+
+ //Delete the object from
+ Task<bool> local = _memCache.DeleteObjectAsync(key, cancellation).AsTask();
+ Task<bool> remote = _backing.DeleteAsync(key, cancellation);
+
+ //task when both complete
+ return Task.WhenAll(local, remote).ContinueWith(static p => p.Result.First(), TaskScheduler.Default);
+ }
+
+ ///<inheritdoc/>
+ public Task<T?> GetAsync<T>(string key, CancellationToken cancellation) => GetAsync<T>(key, _fallbackDeserializer, cancellation);
+
+ ///<inheritdoc/>
+ public async Task<T?> GetAsync<T>(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation)
+ {
+ _ = deserializer ?? throw new ArgumentNullException(nameof(deserializer));
+
+ GetStateResult<T?> state = new()
+ {
+ Deserialzer = deserializer,
+ Value = default!
+ };
+
+ //Try to the object from the cache and if found, deserialize it and store the result
+ await GetAsync(key, static (r, data) => r.SetState(data), state, cancellation);
+
+ return state.Value;
+ }
+
+ ///<inheritdoc/>
+ public async Task GetAsync<T>(string key, ObjectDataSet<T> setter, T state, CancellationToken cancellation)
+ {
+ _ = key ?? throw new ArgumentNullException(nameof(key));
+ _ = setter ?? throw new ArgumentNullException(nameof(setter));
+
+ 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))
+ {
+ setter(state, entry.GetDataSegment());
+ return;
+ }
+ }
+ /*
+ * Can't avoid a double copy because we need to read the data from cache in order to store
+ * a local copy to update memcache
+ */
+
+ //Alloc buffer from client heap
+ using ObjectGetBuffer getBuffer = new(_bufferHeap);
+
+ //Get the object from the server
+ await _backing.GetAsync(key, static (b, data) => b.SetData(data), getBuffer, cancellation);
+
+ //See if object data was set
+ if (!getBuffer.GetData().IsEmpty)
+ {
+ //Update local cache
+ await _memCache.AddOrUpdateObjectAsync(key, null, static b => b.GetData(), getBuffer, DateTime.UtcNow, CancellationToken.None);
+
+ //Invoket the setter
+ setter(state, getBuffer.GetData());
+ }
+ }
+
+ ///<inheritdoc/>
+ public async Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation)
+ {
+ CheckConnected();
+
+ //Alloc serialzation buffer
+ using AddOrUpdateBuffer buffer = new (_bufferHeap);
+
+ //Serialze the value
+ serialzer.Serialize(value, buffer);
+
+ await AddOrUpdateAsync(key, newKey, static p => p.GetData(), buffer, cancellation);
+ }
+
+ ///<inheritdoc/>
+ public async Task AddOrUpdateAsync<T>(string key, string? newKey, ObjectDataReader<T> callback, T state, CancellationToken cancellation)
+ {
+ CheckConnected();
+
+ DateTime currentTime = DateTime.UtcNow;
+
+ try
+ {
+ //Update remote first, and if exceptions are raised, do not update local cache
+ await _backing.AddOrUpdateAsync(key, newKey, callback, state, cancellation);
+
+ //Safe to update local cache
+ await _memCache.AddOrUpdateObjectAsync(key, newKey, callback, state, 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]);
+ }
+ }
+
+ /*
+ * Stores temporary state for a cache get operation
+ * that requires a deserializer to return it to
+ * object form
+ */
+ private sealed class GetStateResult<T>
+ {
+ public T? Value;
+ public ICacheObjectDeserializer? Deserialzer;
+
+ public void SetState(ReadOnlySpan<byte> data)
+ {
+ Value = Deserialzer!.Deserialize<T>(data);
+ }
+ }
+
+ /*
+ * A buffer to store object data on a cache get
+ */
+ private sealed class ObjectGetBuffer : VnDisposeable
+ {
+ 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/plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteCacheOperator.cs
index f40f746..fdf1c5e 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteCacheOperator.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/RemoteCacheOperator.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
+* Package: VNLib.Data.Caching.Providers.VNCache
* File: RemoteCacheOperator.cs
*
-* RemoteCacheOperator.cs is part of VNLib.Plugins.Extensions.VNCache which is
+* RemoteCacheOperator.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -27,10 +27,10 @@ using System.Threading;
using System.Threading.Tasks;
using VNLib.Utils.Logging;
-using VNLib.Data.Caching;
+using VNLib.Plugins;
using VNLib.Plugins.Extensions.Loading;
-namespace VNLib.Plugins.Extensions.VNCache
+namespace VNLib.Data.Caching.Providers.VNCache
{
/// <summary>
/// Represents a handle to a VNCache cache client, that exposes a cancellable
@@ -43,18 +43,20 @@ namespace VNLib.Plugins.Extensions.VNCache
/// </remarks>
public sealed class RemoteCacheOperator : IAsyncBackgroundWork
{
- private readonly VnCacheClient _client;
+ private readonly FBMCacheClient _client;
private CancellationTokenSource? _tokenSource;
- internal RemoteCacheOperator(VnCacheClient client)
+ internal RemoteCacheOperator(FBMCacheClient client, RemoteBackedMemoryCache? memCache)
{
+ //Store the client to be used in the background work
_client = client;
+ Cache = memCache ?? (IGlobalCacheProvider)client; //Cache is the remote backing store
}
/// <summary>
/// The configured global cache instance
/// </summary>
- public IGlobalCacheProvider Cache => _client;
+ public IGlobalCacheProvider Cache { get; }
///<inheritdoc/>
///<exception cref="ArgumentNullException"></exception>
@@ -63,7 +65,7 @@ namespace VNLib.Plugins.Extensions.VNCache
_ = 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))
+ using (_tokenSource = CancellationTokenSource.CreateLinkedTokenSource(exitToken))
{
//Do work with linked source
await _client.DoWorkAsync(pluginLog, _tokenSource.Token)
@@ -78,6 +80,6 @@ namespace VNLib.Plugins.Extensions.VNCache
/// 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/VnGlobalCache.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheClient.cs
index 4ad6560..a832b41 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNCacheClient.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
-* File: VnGlobalCache.cs
+* Package: VNLib.Data.Caching.Providers.VNCache
+* File: VNCacheClient.cs
*
-* VnGlobalCache.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger
-* VNLib collection of libraries and utilities.
+* VNCacheClient.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -28,44 +28,60 @@ using System.Threading;
using System.Threading.Tasks;
using VNLib.Utils.Logging;
-using VNLib.Data.Caching;
+using VNLib.Plugins;
using VNLib.Plugins.Extensions.Loading;
-namespace VNLib.Plugins.Extensions.VNCache
+/*
+ * This package exports an IGlobalCacheProvider that is intended to be packaged by
+ * application distributors that want to use VNCache as a global cache for their
+ * application.
+ *
+ * This package allows for memory only caching, write-through memory cache, and
+ * direct remote caching using VNCache as the backend.
+ */
+
+namespace VNLib.Data.Caching.Providers.VNCache
{
/// <summary>
- /// A wrapper to simplify a shared global cache client
+ /// The VNCache global cache provider client, that is intended to be loaded
+ /// using <see cref="LoadingExtensions.GetOrCreateSingleton{T}(PluginBase)"/> directly
+ /// on the plugin loading a cache client.
+ /// <para>
+ /// Users may also create cache instances outside of plugin context using static
+ /// methods.
+ /// </para>
/// </summary>
- [ConfigurationName(VNCacheExtensions.CACHE_CONFIG_KEY)]
- public sealed class VnGlobalCache : IGlobalCacheProvider
+ [ExternService]
+ [ConfigurationName(CACHE_CONFIG_KEY)]
+ public sealed class VNCacheClient : IGlobalCacheProvider
{
+ internal const string CACHE_CONFIG_KEY = "cache";
+ internal const string MEMORY_CACHE_CONFIG_KEY = "memory_cache";
+ internal const string MEMORY_CACHE_ONLY_KEY = "memory_only";
+
private readonly IGlobalCacheProvider _client;
- /// <summary>
- /// Initializes an emtpy client wrapper that still requires
- /// configuration loading
- /// </summary>
- public VnGlobalCache(PluginBase pbase, IConfigScope config)
+ public VNCacheClient(PluginBase plugin, IConfigScope config)
{
- if (config.TryGetValue(VNCacheExtensions.MEMORY_CACHE_CONFIG_KEY, out _))
+ if (config.TryGetValue(MEMORY_CACHE_CONFIG_KEY, out _))
{
//Check for memory only flag
- if (config.TryGetValue(VNCacheExtensions.MEMORY_CACHE_ONLY_KEY, out JsonElement memOnly) && memOnly.GetBoolean())
+ if (config.TryGetValue(MEMORY_CACHE_ONLY_KEY, out JsonElement memOnly) && memOnly.GetBoolean())
{
//Create a memory-only cache
- _client = pbase.GetOrCreateSingleton<MemoryCache>();
+ _client = plugin.GetOrCreateSingleton<MemoryCache>();
}
else
{
//Remote-backed memory cache
- _client = pbase.GetOrCreateSingleton<RemoteBackedMemoryCache>();
+ _client = plugin.GetOrCreateSingleton<RemoteBackedMemoryCache>();
}
}
else
{
//Setup non-memory backed cache client
- _client = pbase.GetOrCreateSingleton<VnCacheClient>();
+ _client = plugin.GetOrCreateSingleton<FBMCacheClient>();
}
}
@@ -86,14 +102,14 @@ namespace VNLib.Plugins.Extensions.VNCache
_ = config ?? throw new ArgumentNullException(nameof(config));
//Init client
- VnCacheClient client = new(config, debugLog);
+ FBMCacheClient client = new(config, debugLog);
//Return single handle
- return new(client);
+ return new(client, null);
}
/// <summary>
- /// Allows you to programtically create your own instance if a VNCache remote server backed
+ /// Allows you to programatically 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>
@@ -111,11 +127,13 @@ namespace VNLib.Plugins.Extensions.VNCache
_ = remote ?? throw new ArgumentNullException(nameof(remote));
_ = memory ?? throw new ArgumentNullException(nameof(memory));
+ FBMCacheClient client = new(remote, debugLog);
+
//Init client
- RemoteBackedMemoryCache client = new(remote, memory, debugLog);
+ RemoteBackedMemoryCache memCache = new(memory, client);
//Return single handle
- return new(client);
+ return new(client, memCache);
}
/// <summary>
@@ -138,7 +156,6 @@ namespace VNLib.Plugins.Extensions.VNCache
//Return single handle
return new(cache);
}
-
///<inheritdoc/>
public bool IsConnected => _client.IsConnected;
@@ -150,13 +167,19 @@ namespace VNLib.Plugins.Extensions.VNCache
}
///<inheritdoc/>
- public Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation)
+ public Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerializer serialzer, CancellationToken cancellation)
{
return _client.AddOrUpdateAsync(key, newKey, value, serialzer, cancellation);
}
///<inheritdoc/>
- public Task DeleteAsync(string key, CancellationToken cancellation)
+ public Task AddOrUpdateAsync<T>(string key, string? newKey, ObjectDataReader<T> callback, T state, CancellationToken cancellation)
+ {
+ return _client.AddOrUpdateAsync(key, newKey, callback, state, cancellation);
+ }
+
+ ///<inheritdoc/>
+ public Task<bool> DeleteAsync(string key, CancellationToken cancellation)
{
return _client.DeleteAsync(key, cancellation);
}
@@ -168,21 +191,21 @@ namespace VNLib.Plugins.Extensions.VNCache
}
///<inheritdoc/>
- public Task<T?> GetAsync<T>(string key, ICacheObjectDeserialzer deserializer, CancellationToken cancellation)
+ public Task<T?> GetAsync<T>(string key, ICacheObjectDeserializer deserializer, CancellationToken cancellation)
{
return _client.GetAsync<T>(key, deserializer, cancellation);
}
///<inheritdoc/>
- public Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation)
+ public Task GetAsync<T>(string key, ObjectDataSet<T> callback, T state, CancellationToken cancellation)
{
- return _client.GetAsync(key, rawData, cancellation);
+ return _client.GetAsync(key, callback, state, cancellation);
}
///<inheritdoc/>
- public Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation)
+ public object GetUnderlyingStore()
{
- return _client.AddOrUpdateAsync(key, newKey, rawData, cancellation);
+ return _client.GetUnderlyingStore();
}
}
-} \ No newline at end of file
+}
diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNLib.Data.Caching.Providers.VNCache.csproj b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNLib.Data.Caching.Providers.VNCache.csproj
new file mode 100644
index 0000000..93825a5
--- /dev/null
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VNLib.Data.Caching.Providers.VNCache.csproj
@@ -0,0 +1,52 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <RootNamespace>VNLib.Data.Caching.Providers.VNCache</RootNamespace>
+ <AssemblyName>VNLib.Data.Caching.Providers.VNCache</AssemblyName>
+ <Nullable>enable</Nullable>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <ProduceReferenceAssembly>True</ProduceReferenceAssembly>
+ <GenerateDocumentationFile>False</GenerateDocumentationFile>
+ <!-- Resolve nuget dll files and store them in the output dir -->
+ <EnableDynamicLoading>true</EnableDynamicLoading>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>VNLib.Data.Caching.Providers.VNCache</Product>
+ <PackageId>VNLib.Data.Caching.Providers.VNCache</PackageId>
+ <Description>A runtime asset VNCache client library that exposes an IGlobalCacheProvider that works with VNCache clusters, write through memory-cache, and memory only cache</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/plugins/VNLib.Data.Caching.Providers.VNCache</RepositoryUrl>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <PackageLicenseFile>LICENSE</PackageLicenseFile>
+ <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <None Include="..\..\..\LICENSE">
+ <Pack>True</Pack>
+ <PackagePath>\</PackagePath>
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ <None Include="..\README.md">
+ <Pack>True</Pack>
+ <PackagePath>\</PackagePath>
+ </None>
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Loading\src\VNLib.Plugins.Extensions.Loading.csproj" />
+ <ProjectReference Include="..\..\..\lib\VNLib.Data.Caching.Extensions\src\VNLib.Data.Caching.Extensions.csproj" />
+ <ProjectReference Include="..\..\..\lib\VNLib.Data.Caching.ObjectCache\src\VNLib.Data.Caching.ObjectCache.csproj" />
+ <ProjectReference Include="..\..\..\lib\VNLib.Data.Caching\src\VNLib.Data.Caching.csproj" />
+ </ItemGroup>
+
+ <Target Condition="'$(BuildingInsideVisualStudio)' == true" Name="PostBuild" AfterTargets="PostBuildEvent">
+ <Exec Command="start xcopy &quot;$(TargetDir)&quot; &quot;..\..\..\..\..\devplugins\runtimeassets\$(TargetName)&quot; /E /Y /R" />
+ </Target>
+
+</Project>
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClientConfig.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VnCacheClientConfig.cs
index bfa9d92..f84fe55 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClientConfig.cs
+++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/VnCacheClientConfig.cs
@@ -2,18 +2,18 @@
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extensions.VNCache
+* Package: VNLib.Data.Caching.Providers.VNCache
* File: VnCacheClientConfig.cs
*
-* VnCacheClientConfig.cs is part of VNLib.Plugins.Extensions.VNCache which is part of the larger
+* VnCacheClientConfig.cs is part of VNLib.Data.Caching.Providers.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
+* VNLib.Data.Caching.Providers.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,
+* VNLib.Data.Caching.Providers.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.
@@ -28,7 +28,7 @@ using System.Text.Json.Serialization;
using VNLib.Plugins.Extensions.Loading;
-namespace VNLib.Plugins.Extensions.VNCache
+namespace VNLib.Data.Caching.Providers.VNCache
{
/// <summary>
/// Represents a remote VNCache client configuration
@@ -53,7 +53,7 @@ namespace VNLib.Plugins.Extensions.VNCache
/// The time (in seconds) to randomly delay polling the broker server
/// for available servers
/// </summary>
- [JsonPropertyName("discovery_interval_Sec")]
+ [JsonPropertyName("discovery_interval_sec")]
public int? DiscoveryIntervalSeconds { get; set; }
/// <summary>
@@ -104,11 +104,11 @@ namespace VNLib.Plugins.Extensions.VNCache
if (!DiscoveryIntervalSeconds.HasValue || DiscoveryIntervalSeconds.Value < 1)
{
- throw new ArgumentException("You must specify a retry interval period greater than 0", "retry_interval_sec");
+ throw new ArgumentException("You must specify a discovery 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)
+ if (!RequestTimeoutSeconds.HasValue || RequestTimeoutSeconds.Value < 0)
{
throw new ArgumentException("You must specify a positive integer FBM message timoeut", "request_timeout_sec");
}
@@ -128,12 +128,12 @@ namespace VNLib.Plugins.Extensions.VNCache
}
//Verify http connection
- if(peer.Scheme != Uri.UriSchemeHttp && peer.Scheme != Uri.UriSchemeHttps)
+ 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/vnlib.data.caching.build.sln b/vnlib.data.caching.build.sln
index b3dae6c..c0d0776 100644
--- a/vnlib.data.caching.build.sln
+++ b/vnlib.data.caching.build.sln
@@ -29,6 +29,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Taskfile.yaml = Taskfile.yaml
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VNLib.Data.Caching.Providers.VNCache", "plugins\VNLib.Data.Caching.Providers.VNCache\src\VNLib.Data.Caching.Providers.VNCache.csproj", "{EE352860-AC6F-4938-9F7C-F251CF9AF7B4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VNLib.Data.Caching.Providers.Redis", "plugins\VNLib.Data.Caching.Providers.Redis\src\VNLib.Data.Caching.Providers.Redis.csproj", "{AA412A81-C03E-49C1-8E78-26E3310D06EF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -55,6 +59,14 @@ Global
{AAECEC05-2439-47B4-A50D-D46EDDFA420C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAECEC05-2439-47B4-A50D-D46EDDFA420C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAECEC05-2439-47B4-A50D-D46EDDFA420C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EE352860-AC6F-4938-9F7C-F251CF9AF7B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EE352860-AC6F-4938-9F7C-F251CF9AF7B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EE352860-AC6F-4938-9F7C-F251CF9AF7B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EE352860-AC6F-4938-9F7C-F251CF9AF7B4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AA412A81-C03E-49C1-8E78-26E3310D06EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AA412A81-C03E-49C1-8E78-26E3310D06EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AA412A81-C03E-49C1-8E78-26E3310D06EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AA412A81-C03E-49C1-8E78-26E3310D06EF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -65,6 +77,8 @@ Global
{FEDACFD5-2529-4E37-8D11-3E0143954368} = {FBF12BFF-51A7-43BD-A066-D2FFACF333AA}
{E21ED553-4B08-41A5-B329-4BAF7701A99F} = {FBF12BFF-51A7-43BD-A066-D2FFACF333AA}
{AAECEC05-2439-47B4-A50D-D46EDDFA420C} = {39E35C2F-BFBB-4819-B66A-475057D389EB}
+ {EE352860-AC6F-4938-9F7C-F251CF9AF7B4} = {39E35C2F-BFBB-4819-B66A-475057D389EB}
+ {AA412A81-C03E-49C1-8E78-26E3310D06EF} = {39E35C2F-BFBB-4819-B66A-475057D389EB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C1214A47-444A-432F-8CF8-FDF2B1FA1CEC}