aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Plugins.Extensions.VNCache/src
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-07-04 23:10:34 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-07-04 23:10:34 -0400
commitb409ce938709d153c42f8127def776cb81cc8c26 (patch)
treedd90eb8dc4eaad5c18a3a2a60d94a57a6cd9ffda /lib/VNLib.Plugins.Extensions.VNCache/src
parent223b2c1bb84710e735405cca6b79b9a15e937887 (diff)
latest entity caching primitives
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.VNCache/src')
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheBuilder.cs94
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs123
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs131
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheExpirationPolicy.cs49
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheTaskPolicy.cs (renamed from lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheExpirationStrategy.cs)26
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCacheKey.cs39
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityStore.cs61
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/InfiniteCacheExpirationPolicy.cs38
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/TransparentEntityCache.cs74
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/WriteBackCachePolicy.cs70
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/WriteThroughCachePolicy.cs45
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs2
12 files changed, 649 insertions, 103 deletions
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheBuilder.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheBuilder.cs
new file mode 100644
index 0000000..ea1566d
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheBuilder.cs
@@ -0,0 +1,94 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.VNCache
+* File: EntityCacheBuilder.cs
+*
+* EntityCacheBuilder.cs is part of VNLib.Plugins.Extensions.VNCache
+* which is part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.VNCache is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.VNCache is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading.Tasks;
+
+using VNLib.Utils.Logging;
+
+namespace VNLib.Plugins.Extensions.VNCache.DataModel
+{
+ /// <summary>
+ /// Represents a cache that can store entities by their unique key
+ /// </summary>
+ /// <typeparam name="TEntity"></typeparam>
+ /// <param name="cache"></param>
+ public sealed class EntityCacheBuilder<TEntity>(IEntityCache<TEntity> cache)
+ where TEntity : class
+ {
+ ICacheExpirationPolicy<TEntity>? _expPolicy;
+ ICacheTaskPolicy? _taskPolicy;
+
+ public EntityCacheBuilder<TEntity> WithExpirationPoicy(ICacheExpirationPolicy<TEntity> expiration)
+ {
+ _expPolicy = expiration;
+ return this;
+ }
+
+ public EntityCacheBuilder<TEntity> WithInfiniteExpiration()
+ {
+ _expPolicy = new InfiniteCacheExpirationPolicy<TEntity>();
+ return this;
+ }
+
+ public EntityCacheBuilder<TEntity> WithTaskPolicy(ICacheTaskPolicy taskPolicy)
+ {
+ _taskPolicy = taskPolicy;
+ return this;
+ }
+
+ public EntityCacheBuilder<TEntity> WithWriteBackPolicy(Action<Task> onFaulted)
+ {
+ _taskPolicy = new WriteBackCachePolicy(onFaulted);
+ return this;
+ }
+
+ public EntityCacheBuilder<TEntity> WithWriteBackPolicy(ILogProvider logger, LogLevel logLevel = LogLevel.Warning)
+ {
+ _taskPolicy = new WriteBackCachePolicy((t) =>
+ {
+ logger.Write(logLevel, t.Exception!, "Background task operation failed with exception: ");
+ });
+
+ return this;
+ }
+
+ public EntityCacheBuilder<TEntity> WithWriteThoughPolicy()
+ {
+ _taskPolicy = WriteThroughCachePolicy.Instance;
+ return this;
+ }
+
+
+ public EntityResultCache<TEntity> Build()
+ {
+ ArgumentNullException.ThrowIfNull(_expPolicy, "Expiration");
+ ArgumentNullException.ThrowIfNull(cache);
+ ArgumentNullException.ThrowIfNull(_taskPolicy, "TaskPolicy");
+
+ return new EntityResultCache<TEntity>(cache, _taskPolicy, _expPolicy);
+ }
+ }
+
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs
index 6b39580..a26da27 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs
@@ -57,8 +57,9 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
/// <exception cref="ArgumentNullException"></exception>
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));
+ ArgumentNullException.ThrowIfNull(entity);
+ ArgumentNullException.ThrowIfNull(cache);
+
//Delete by its id
return cache.RemoveAsync(entity.Id, cancellation);
}
@@ -94,9 +95,9 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
/// <exception cref="ArgumentNullException"></exception>
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));
- _ = deserializer ?? throw new ArgumentNullException(nameof(deserializer));
+ ArgumentNullException.ThrowIfNull(cache);
+ ArgumentNullException.ThrowIfNull(serialier);
+ ArgumentNullException.ThrowIfNull(deserializer);
return new EntityCacheImpl<T>(cache, deserializer, serialier);
}
@@ -112,96 +113,52 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
/// <exception cref="ArgumentNullException"></exception>
public static IEntityCache<T> CreateJsonEntityCache<T>(this IGlobalCacheProvider cache, int bufferSize) where T: class
{
- _ = cache ?? throw new ArgumentNullException(nameof(cache));
+ ArgumentNullException.ThrowIfNull(cache);
+
JsonCacheObjectSerializer json = new(bufferSize);
return CreateEntityCache<T>(cache, json, json);
}
/// <summary>
- /// Attemts to recover an entity from cache if possible, if a miss occurs, the
- /// factory function is called to produce a value from a backing store. If the store
- /// returns a result it is writen back to the cache before this method returns
+ /// Creates a new <see cref="EntityCacheBuilder{TEntity}"/> instance for building a
+ /// new <see cref="EntityResultCache{TEntity}"/>
/// </summary>
- /// <typeparam name="T"></typeparam>
+ /// <typeparam name="TEntity">The result entity type</typeparam>
/// <param name="cache"></param>
- /// <param name="id">The id of the entity to get or laod</param>
- /// <param name="factory">The factory callback function to produce a value when a cache miss occurs</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>A task that completes by returning the entity</returns>
+ /// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
- public static async Task<T?> GetOrLoadAsync<T>(this IEntityCache<T> cache, string id, Func<string, Task<T?>> factory, CancellationToken cancellation = default) where T : class
+ public static EntityCacheBuilder<TEntity> CreateResultCache<TEntity>(this IEntityCache<TEntity> cache)
+ where TEntity : class
{
- _ = cache ?? throw new ArgumentNullException(nameof(cache));
- _ = id ?? throw new ArgumentNullException(nameof(id));
- _ = factory ?? throw new ArgumentNullException(nameof(factory));
-
- //try to load the value from cache
- T? record = await cache.GetAsync(id, cancellation);
-
- //If record was not found in cache, load it from the factory
- if (record is null)
- {
- record = await factory(id);
-
- //If new record found, write to cache
- if (record is not null)
- {
- await cache.UpsertAsync(id, record, cancellation);
- }
- }
-
- return record;
+ ArgumentNullException.ThrowIfNull(cache);
+ return new EntityCacheBuilder<TEntity>(cache);
}
/// <summary>
- /// Attemts to recover an entity from cache if possible, if a miss occurs, the
- /// factory function is called to produce a value from a backing store. If the store
- /// returns a result it is writen back to the cache before this method returns
+ /// Builds a transparent entity result cache for a backing store.
/// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="cache"></param>
- /// <param name="id">The id of the entity to get or laod</param>
- /// <param name="factory">The factory callback function to produce a value when a cache miss occurs</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>A task that completes by returning the entity</returns>
- /// <exception cref="ArgumentException"></exception>
- /// <exception cref="ArgumentNullException"></exception>
- public static async Task<T?> GetOrLoadAsync<T>(this IEntityCache<T> cache, string id, Func<string, CancellationToken, Task<T?>> factory, CancellationToken cancellation = default) where T : class
+ /// <typeparam name="TEntity"></typeparam>
+ /// <param name="builder"></param>
+ /// <param name="store">The backing data store used to fetch results from</param>
+ /// <returns>The new <see cref="TransparentEntityCache{TEntity}"/> that wraps the entity store</returns>
+ public static TransparentEntityCache<TEntity> BuildTransparent<TEntity>(this EntityCacheBuilder<TEntity> builder, IEntityStore<TEntity> store)
+ where TEntity : class
{
- ArgumentNullException.ThrowIfNull(cache);
- ArgumentNullException.ThrowIfNull(factory);
- ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(store);
- //try to load the value from cache
- T? record = await cache.GetAsync(id, cancellation);
+ EntityResultCache<TEntity> resultCache = builder.Build();
- //If record was not found in cache, load it from the factory
- if (record is null)
- {
- record = await factory(id, cancellation);
-
- //If new record found, write to cache
- if(record is not null)
- {
- await cache.UpsertAsync(id, record, cancellation);
- }
- }
+ return new TransparentEntityCache<TEntity>(store, resultCache);
+ }
+
- return record;
- }
-
- private sealed class EntityCacheImpl<T> : IEntityCache<T> where T : class
+ private sealed class EntityCacheImpl<T>(IGlobalCacheProvider cache, ICacheObjectDeserializer deserializer, ICacheObjectSerializer serializer)
+ : IEntityCache<T> where T : class
{
- private readonly IGlobalCacheProvider _cacheProvider;
- private readonly ICacheObjectDeserializer _cacheObjectDeserialzer;
- private readonly ICacheObjectSerializer _cacheObjectSerialzer;
-
- public EntityCacheImpl(IGlobalCacheProvider cache, ICacheObjectDeserializer deserializer, ICacheObjectSerializer serializer)
- {
- _cacheProvider = cache;
- _cacheObjectDeserialzer = deserializer;
- _cacheObjectSerialzer = serializer;
- }
+ private readonly IGlobalCacheProvider _cacheProvider = cache;
+ private readonly ICacheObjectDeserializer _cacheObjectDeserialzer = deserializer;
+ private readonly ICacheObjectSerializer _cacheObjectSerialzer = serializer;
///<inheritdoc/>
public Task<T?> GetAsync(string id, CancellationToken token = default) => _cacheProvider.GetAsync<T>(id, _cacheObjectDeserialzer, token);
@@ -213,9 +170,9 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
public Task UpsertAsync(string id, T entity, CancellationToken token = default) => _cacheProvider.AddOrUpdateAsync(id, null, entity, _cacheObjectSerialzer, token);
}
- private sealed class ScopedCacheImpl: ScopedCache
+ private sealed class ScopedCacheImpl(IGlobalCacheProvider cache, ICacheKeyGenerator keyGen) : ScopedCache
{
- private readonly IGlobalCacheProvider Cache;
+ private readonly IGlobalCacheProvider Cache = cache;
///<inheritdoc/>
public override bool IsConnected
@@ -225,7 +182,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
}
///<inheritdoc/>
- protected override ICacheKeyGenerator KeyGen { get; }
+ protected override ICacheKeyGenerator KeyGen { get; } = keyGen;
///<inheritdoc/>
public override ICacheObjectDeserializer DefaultDeserializer => Cache.DefaultDeserializer;
@@ -233,12 +190,6 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel
///<inheritdoc/>
public override ICacheObjectSerializer DefaultSerializer => Cache.DefaultSerializer;
- public ScopedCacheImpl(IGlobalCacheProvider cache, ICacheKeyGenerator keyGen)
- {
- this.Cache = cache;
- KeyGen = keyGen;
- }
-
///<inheritdoc/>
public override Task<bool> DeleteAsync(string key, CancellationToken cancellation)
{
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs
new file mode 100644
index 0000000..fb75fc6
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityResultCache.cs
@@ -0,0 +1,131 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.VNCache
+* File: EntityResultCache.cs
+*
+* EntityResultCache.cs is part of VNLib.Plugins.Extensions.VNCache
+* which is part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.VNCache is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.VNCache is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Plugins.Extensions.VNCache.DataModel
+{
+ /// <summary>
+ /// Represents a cache that can store entities by their unique key
+ /// using a user-provided backing store and custom request state.
+ /// </summary>
+ /// <typeparam name="TEntity"></typeparam>
+ /// <param name="cache">The cache backing store</param>
+ /// <param name="taskPolicy">Specifies how background cache tasks are handled</param>
+ /// <param name="expirationPolicy">The result expiration policy</param>
+ public class EntityResultCache<TEntity>(
+ IEntityCache<TEntity> cache,
+ ICacheTaskPolicy taskPolicy,
+ ICacheExpirationPolicy<TEntity> expirationPolicy
+ )
+ where TEntity : class
+ {
+
+ /// <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>(
+ TRequest request,
+ Func<TRequest, CancellationToken, Task<TEntity?>> resultFactory,
+ CancellationToken cancellation = default
+ ) where TRequest : IEntityCacheKey
+ {
+ ArgumentNullException.ThrowIfNull(request);
+ ArgumentNullException.ThrowIfNull(resultFactory);
+ cancellation.ThrowIfCancellationRequested();
+
+ string key = request.GetKey();
+
+ //try to fetch from cache
+ TEntity? entity = await cache.GetAsync(key, cancellation);
+
+ if (entity is not null)
+ {
+ //Check if the entity is expired
+ if (expirationPolicy.IsExpired(entity))
+ {
+ //Setting to null will force a cache miss
+ entity = null;
+ }
+ }
+
+ if (entity is null)
+ {
+ //Cache miss, load from factory
+ entity = await resultFactory(request, cancellation);
+
+ if (entity is not null)
+ {
+ //Notify the expiration policy that the entity was refreshed before writing back to cache
+ expirationPolicy.OnRefreshed(entity);
+
+ //Fresh entity was fetched from the factory so write to cache
+ Task upsert = cache.UpsertAsync(key, entity, cancellation);
+
+ //Allow task policy to determine how completions are observed
+ await taskPolicy.ObserveOperationAsync(upsert);
+ }
+ }
+
+ return entity;
+ }
+
+ /// <summary>
+ /// Removes an entity from the cache by it's request entity
+ /// </summary>
+ /// <param name="request">The request entity to retrieve the entity key from</param>
+ /// <param name="cancellation">A token to cancel the async operation</param>
+ /// <returns>A task that completes when the key is removed, based on the task policy</returns>
+ public Task RemoveAsync<TRequest>(TRequest request, CancellationToken cancellation = default)
+ where TRequest : IEntityCacheKey
+ {
+ ArgumentNullException.ThrowIfNull(request);
+
+ string key = request.GetKey();
+
+ return cache.RemoveAsync(key, cancellation);
+ }
+
+ /// <summary>
+ /// Removes an entity from the cache by it's request entity
+ /// </summary>
+ /// <param name="key">The entities unique key</param>
+ /// <param name="cancellation">A token to cancel the async operation</param>
+ /// <returns>A task that completes when the key is removed, based on the task policy</returns>
+ public Task RemoveAsync(string key, CancellationToken cancellation = default)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(key);
+
+ Task remove = cache.RemoveAsync(key, cancellation);
+
+ return taskPolicy.ObserveOperationAsync(remove);
+ }
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheExpirationPolicy.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheExpirationPolicy.cs
new file mode 100644
index 0000000..60f1f22
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheExpirationPolicy.cs
@@ -0,0 +1,49 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.VNCache
+* File: ICacheExpirationPolicy.cs
+*
+* ICacheExpirationPolicy.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/.
+*/
+
+namespace VNLib.Plugins.Extensions.VNCache.DataModel
+{
+ /// <summary>
+ /// Provides an expiration policy for cache entities
+ /// </summary>
+ /// <typeparam name="TEntity"></typeparam>
+ public interface ICacheExpirationPolicy<TEntity>
+ {
+ /// <summary>
+ /// Determines if the entity is expired and should be
+ /// reloaded from the backing store.
+ /// </summary>
+ /// <param name="result">The entity to check status of</param>
+ /// <returns>True of the entity has expired and should be reloaded, false if it is still a valid result</returns>
+ bool IsExpired(TEntity result);
+
+ /// <summary>
+ /// Fired directly after the entity has been refreshed
+ /// </summary>
+ /// <param name="entity">The entity freshly fetched from the backing store</param>
+ virtual void OnRefreshed(TEntity entity)
+ { }
+ }
+
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheExpirationStrategy.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheTaskPolicy.cs
index 299834b..4824937 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheExpirationStrategy.cs
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ICacheTaskPolicy.cs
@@ -1,11 +1,11 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.VNCache
-* File: ICacheExpirationStrategy.cs
+* File: ICacheTaskPolicy.cs
*
-* ICacheExpirationStrategy.cs is part of VNLib.Plugins.Extensions.VNCache
+* ICacheTaskPolicy.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
@@ -22,27 +22,21 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-using System;
+using System.Threading.Tasks;
namespace VNLib.Plugins.Extensions.VNCache.DataModel
{
/// <summary>
- /// An interface that provides an object caching expiration
- /// instructions
+ /// Provides a policy for observing the completion of cache operations
/// </summary>
- public interface ICacheExpirationStrategy
+ public interface ICacheTaskPolicy
{
/// <summary>
- /// The maxium age of a given entity
+ /// Observes the completion of a cache operation
/// </summary>
- TimeSpan CacheMaxAge { get; }
-
- /// <summary>
- /// Invoked when a record is retrieved and determined to be expired
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="expired"></param>
- void OnExpired<T>(T expired);
+ /// <param name="operation">The task representing the operation to observe</param>
+ /// <returns>A task that the caller will await on the current control flow</returns>
+ Task ObserveOperationAsync(Task operation);
}
}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCacheKey.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCacheKey.cs
new file mode 100644
index 0000000..8416a40
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCacheKey.cs
@@ -0,0 +1,39 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.VNCache
+* File: IEntityCacheKey.cs
+*
+* IEntityCacheKey.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/.
+*/
+
+namespace VNLib.Plugins.Extensions.VNCache.DataModel
+{
+ /// <summary>
+ /// When implemnted on an entity object, gets a deterministic cache key for the entity
+ /// </summary>
+ public interface IEntityCacheKey
+ {
+ /// <summary>
+ /// Gets the key for the current entity request
+ /// </summary>
+ /// <returns>The cache key string unique to the stored record</returns>
+ string GetKey();
+ }
+
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityStore.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityStore.cs
new file mode 100644
index 0000000..29282cf
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityStore.cs
@@ -0,0 +1,61 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.VNCache
+* File: IEntityStore.cs
+*
+* IEntityStore.cs is part of VNLib.Plugins.Extensions.VNCache
+* which is part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.VNCache is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.VNCache is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Plugins.Extensions.VNCache.DataModel
+{
+ /// <summary>
+ /// An instance that stores entities which can be fetched, updated, or removed
+ /// </summary>
+ /// <typeparam name="TEntity">The entity result type</typeparam>
+ public interface IEntityStore<TEntity>
+ {
+ /// <summary>
+ /// Fetches an entity from the store by it's request entity state object
+ /// </summary>
+ /// <param name="request">The request state object</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that yields the entity object if it exists</returns>
+ Task<TEntity?> GetAsync<TRequest>(TRequest request, CancellationToken cancellation = default) where TRequest : IEntityCacheKey;
+
+ /// <summary>
+ /// Updates or inserts an entity into the store
+ /// </summary>
+ /// <param name="entity">The entity instance to store in the database</param>
+ /// <param name="request">The request state object</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that completes when the upsert operation has completed</returns>
+ Task UpsertAsync<TRequest>(TRequest request, TEntity entity, CancellationToken cancellation = default) where TRequest : IEntityCacheKey;
+
+ /// <summary>
+ /// Removes an entity from the store
+ /// </summary>
+ /// <param name="request">The request state object</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that completes with the result of the delete operation</returns>
+ Task<bool> RemoveAsync<TRequest>(TRequest request, CancellationToken cancellation = default) where TRequest : IEntityCacheKey;
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/InfiniteCacheExpirationPolicy.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/InfiniteCacheExpirationPolicy.cs
new file mode 100644
index 0000000..b28ee84
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/InfiniteCacheExpirationPolicy.cs
@@ -0,0 +1,38 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.VNCache
+* File: InfiniteCacheExpirationPolicy.cs
+*
+* InfiniteCacheExpirationPolicy.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/.
+*/
+
+namespace VNLib.Plugins.Extensions.VNCache.DataModel
+{
+ /// <summary>
+ /// A cache exiration policy that never expires entities
+ /// from the cache
+ /// </summary>
+ /// <typeparam name="TEntity">The entity type</typeparam>
+ public class InfiniteCacheExpirationPolicy<TEntity> : ICacheExpirationPolicy<TEntity>
+ {
+ ///<inheritdoc/>
+ public bool IsExpired(TEntity result) => false;
+ }
+
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/TransparentEntityCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/TransparentEntityCache.cs
new file mode 100644
index 0000000..90aeb74
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/TransparentEntityCache.cs
@@ -0,0 +1,74 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.VNCache
+* File: EntityCacheExtensions.cs
+*
+* EntityCacheExtensions.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.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Plugins.Extensions.VNCache.DataModel
+{
+ /// <summary>
+ /// A cache proxy that sits transparently between a backing store and the caller
+ /// to cache unique entities
+ /// </summary>
+ /// <typeparam name="TEntity"></typeparam>
+ /// <param name="store">The backing entity data store</param>
+ /// <param name="cache">The entity cache used to fetch</param>
+ public class TransparentEntityCache<TEntity>(IEntityStore<TEntity> store, EntityResultCache<TEntity> cache) : IEntityStore<TEntity>
+ where TEntity : class
+ {
+
+ ///<inheritdoc/>
+ public Task<TEntity?> GetAsync<TRequest>(TRequest request, CancellationToken cancellation = default)
+ where TRequest : IEntityCacheKey
+ {
+ return cache.FetchAsync(request, store.GetAsync, cancellation);
+ }
+
+ ///<inheritdoc/>
+ public Task<bool> RemoveAsync<TRequest>(TRequest request, CancellationToken cancellation = default)
+ where TRequest : IEntityCacheKey
+ {
+ Task<bool> _fromCache = cache.RemoveAsync(request, cancellation)
+ .ContinueWith(static (_) => true, TaskScheduler.Default);
+
+ Task<bool> _fromStore = store.RemoveAsync(request, cancellation);
+
+ return Task.WhenAll(_fromCache, _fromStore)
+ .ContinueWith(static (t) => t.Result.All(static r => r), TaskScheduler.Default);
+ }
+
+ ///<inheritdoc/>
+ public Task UpsertAsync<TRequest>(TRequest request, TEntity entity, CancellationToken cancellation = default)
+ where TRequest : IEntityCacheKey
+ {
+ //Remove key from cache but push update to store
+ Task _fromCache = cache.RemoveAsync(request, cancellation);
+
+ Task _fromStore = store.UpsertAsync(request, entity, cancellation);
+
+ return Task.WhenAll(_fromCache, _fromStore);
+ }
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/WriteBackCachePolicy.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/WriteBackCachePolicy.cs
new file mode 100644
index 0000000..93434de
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/WriteBackCachePolicy.cs
@@ -0,0 +1,70 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.VNCache
+* File: WriteBackCachePolicy.cs
+*
+* WriteBackCachePolicy.cs is part of VNLib.Plugins.Extensions.VNCache
+* which is part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.VNCache is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.VNCache is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Threading.Tasks;
+using System.Diagnostics;
+
+namespace VNLib.Plugins.Extensions.VNCache.DataModel
+{
+ /// <summary>
+ /// Provides a policy for observing the completion of cache operations
+ /// </summary>
+ /// <param name="onFaulted">
+ /// A callback function that is executed when a cache operation has fauled due to an exception
+ /// </param>
+ public class WriteBackCachePolicy(Action<Task> onFaulted) : ICacheTaskPolicy
+ {
+ ///<inheritdoc/>
+ public Task ObserveOperationAsync(Task operation)
+ {
+ ArgumentNullException.ThrowIfNull(operation);
+
+ if (!operation.IsCompleted)
+ {
+ //Defer the observation to the callback function to watch for errors
+ _ = operation.ContinueWith(
+ ObserveOperation,
+ cancellationToken: default,
+ TaskContinuationOptions.ExecuteSynchronously,
+ TaskScheduler.Default
+ );
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private void ObserveOperation(Task operation)
+ {
+ //Should only be called on a completed task
+ Debug.Assert(operation.IsCompleted);
+
+ if (operation.IsFaulted)
+ {
+ onFaulted(operation);
+ }
+ }
+ }
+
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/WriteThroughCachePolicy.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/WriteThroughCachePolicy.cs
new file mode 100644
index 0000000..9a3e0fc
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/WriteThroughCachePolicy.cs
@@ -0,0 +1,45 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.VNCache
+* File: WriteThroughCachePolicy.cs
+*
+* WriteThroughCachePolicy.cs is part of VNLib.Plugins.Extensions.VNCache
+* which is part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.VNCache is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.VNCache is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading.Tasks;
+
+namespace VNLib.Plugins.Extensions.VNCache.DataModel
+{
+ /// <summary>
+ /// Provides a very simple Write-Through cache policy that
+ /// will cause fetch functions to block until the cache is
+ /// consistent with the backing store
+ /// </summary>
+ public class WriteThroughCachePolicy : ICacheTaskPolicy
+ {
+ /// <summary>
+ /// The singleton instance of the write-through cache policy
+ /// </summary>
+ public static readonly WriteThroughCachePolicy Instance = new();
+
+ ///<inheritdoc/>
+ public Task ObserveOperationAsync(Task operation) => operation; //Write through
+ }
+
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs
index f28ea74..a7f3835 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/VNCacheExtensions.cs
@@ -55,7 +55,7 @@ namespace VNLib.Plugins.Extensions.VNCache
/// <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);
+ => plugin.CreateServiceExternal<IGlobalCacheProvider>(asmDllPath, search, defaultCtx: null);
/// <summary>
/// Gets the configuration assigned global cache provider, if defined. If the configuration does not