aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs')
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs180
1 files changed, 176 insertions, 4 deletions
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs
index fb75fc6..56311f0 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs
@@ -45,13 +45,31 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
{
/// <summary>
+ /// The backing entity cache store
+ /// </summary>
+ public IEntityCache<TEntity> Cache => cache;
+
+ /// <summary>
+ /// The task policy for which this result cache will
+ /// respect
+ /// </summary>
+ public ICacheTaskPolicy TaskPolicy => taskPolicy;
+
+ /// <summary>
+ /// The expiration policy for which this result cache will
+ /// respect for entity expiration and refreshing
+ /// </summary>
+ public ICacheExpirationPolicy<TEntity> ExpirationPolicy => expirationPolicy;
+
+
+ /// <summary>
/// Fetchs a result by it's request entity
/// </summary>
/// <param name="request">The fetch request state object</param>
/// <param name="cancellation">A token to canel the operation</param>
/// <param name="resultFactory">A callback generator function</param>
/// <returns>A task the returns the result of the requested entity, or null if it was not found or provided by the backing store</returns>
- public async Task<TEntity?> FetchAsync<TRequest>(
+ public Task<TEntity?> FetchAsync<TRequest>(
TRequest request,
Func<TRequest, CancellationToken, Task<TEntity?>> resultFactory,
CancellationToken cancellation = default
@@ -59,9 +77,48 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
{
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(resultFactory);
- cancellation.ThrowIfCancellationRequested();
- string key = request.GetKey();
+ return FetchAsync(
+ key: request.GetKey(),
+ state: (resultFactory, request),
+ resultFactory: static (rf, c) => rf.resultFactory(rf.request, c),
+ cancellation
+ );
+ }
+
+ /// <summary>
+ /// Fetchs a result by it's request entity
+ /// </summary>
+ /// <param name="key">The fetch request state object</param>
+ /// <param name="cancellation">A token to canel the operation</param>
+ /// <param name="resultFactory">A callback generator function</param>
+ /// <returns>A task the returns the result of the requested entity, or null if it was not found or provided by the backing store</returns>
+ public Task<TEntity?> FetchAsync(
+ string key,
+ Func<CancellationToken, Task<TEntity?>> resultFactory,
+ CancellationToken cancellation = default
+ )
+ {
+ ArgumentNullException.ThrowIfNull(resultFactory);
+
+ return FetchAsync(
+ key,
+ state: resultFactory,
+ resultFactory: static (rf, c) => rf(c),
+ cancellation
+ );
+ }
+
+ private async Task<TEntity?> FetchAsync<TState>(
+ string key,
+ TState state,
+ Func<TState, CancellationToken, Task<TEntity?>> resultFactory,
+ CancellationToken cancellation = default
+ )
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(key);
+ ArgumentNullException.ThrowIfNull(resultFactory);
+ cancellation.ThrowIfCancellationRequested();
//try to fetch from cache
TEntity? entity = await cache.GetAsync(key, cancellation);
@@ -79,7 +136,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
if (entity is null)
{
//Cache miss, load from factory
- entity = await resultFactory(request, cancellation);
+ entity = await resultFactory(state, cancellation);
if (entity is not null)
{
@@ -127,5 +184,120 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
return taskPolicy.ObserveOperationAsync(remove);
}
+
+ /// <summary>
+ /// Performs a cache replacement operation. That is substitutes an exiting
+ /// value with a new one, or inserts a new value if the key does not exist.
+ /// </summary>
+ /// <param name="request">The operation request state object</param>
+ /// <param name="entity">The entity object to store</param>
+ /// <param name="action">A generic callback function to invoke in parallel with the upsert operation</param>
+ /// <param name="cancellation">A token to cancel the async operation</param>
+ /// <returns>
+ /// A task that completes when the upsert operation has completed according to the
+ /// <see cref="TaskPolicy"/>
+ /// </returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public Task UpsertAsync<TRequest>(
+ TRequest request,
+ TEntity entity,
+ Func<TRequest, TEntity, CancellationToken, Task> action,
+ CancellationToken cancellation = default
+ ) where TRequest : IEntityCacheKey
+ {
+ ArgumentNullException.ThrowIfNull(request);
+ ArgumentNullException.ThrowIfNull(action);
+
+ return UpsertAsync(
+ key: request.GetKey(),
+ entity: entity,
+ state: (action, request),
+ callback: static (cb, e, c) => cb.action.Invoke(cb.request, e, c),
+ cancellation
+ );
+ }
+
+
+ /// <summary>
+ /// Performs a cache replacement operation. That is substitutes an exiting
+ /// value with a new one, or inserts a new value if the key does not exist.
+ /// </summary>
+ /// <param name="key">The entity's unique id within the cache store</param>
+ /// <param name="entity">The entity object to store</param>
+ /// <param name="action">A generic callback function to invoke in parallel with the upsert operation</param>
+ /// <param name="cancellation">A token to cancel the async operation</param>
+ /// <returns>
+ /// A task that completes when the upsert operation has completed according to the
+ /// <see cref="TaskPolicy"/>
+ /// </returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public Task UpsertAsync(
+ string key,
+ TEntity entity,
+ Func<TEntity, CancellationToken, Task> action,
+ CancellationToken cancellation = default
+ )
+ {
+ ArgumentNullException.ThrowIfNull(action);
+
+ return UpsertAsync(
+ key,
+ entity,
+ state: action,
+ callback: static (cb, e, c) => cb.Invoke(e, c),
+ cancellation
+ );
+ }
+
+ /// <summary>
+ /// Performs a cache replacement operation. That is substitutes an exiting
+ /// value with a new one, or inserts a new value if the key does not exist.
+ /// </summary>
+ /// <param name="key">The entity's unique id within the cache store</param>
+ /// <param name="entity">The entity object to store</param>
+ /// <param name="cancellation">A token to cancel the async operation</param>
+ /// <returns>
+ /// A task that completes when the upsert operation has completed according to the
+ /// <see cref="TaskPolicy"/>
+ /// </returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public Task UpsertAsync(string key, TEntity entity, CancellationToken cancellation = default)
+ {
+ return UpsertAsync<object?>(
+ key,
+ entity,
+ state: null,
+ callback: static (_, _, _) => Task.CompletedTask,
+ cancellation
+ );
+ }
+
+ private Task UpsertAsync<TState>(
+ string key,
+ TEntity entity,
+ TState state,
+ Func<TState, TEntity, CancellationToken, Task> callback,
+ CancellationToken cancellation = default
+ )
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(key);
+ ArgumentNullException.ThrowIfNull(callback);
+ cancellation.ThrowIfCancellationRequested();
+
+ //Call refresh before storing the entity incase any setup needs to be performed
+ expirationPolicy.OnRefreshed(entity);
+
+ //Cache task must be observed by the task policy
+ Task upsert = taskPolicy.ObserveOperationAsync(
+ operation: cache.UpsertAsync(key, entity, cancellation)
+ );
+
+ Task cbResult = callback(state, entity, cancellation);
+
+ //Combine the observed task and the callback function
+ return Task.WhenAll(cbResult, upsert);
+ }
}
}