diff options
Diffstat (limited to 'VNLib.Data.Caching.Global')
5 files changed, 235 insertions, 0 deletions
diff --git a/VNLib.Data.Caching.Global/Exceptions/CacheNotLoadedException.cs b/VNLib.Data.Caching.Global/Exceptions/CacheNotLoadedException.cs new file mode 100644 index 0000000..c3a3800 --- /dev/null +++ b/VNLib.Data.Caching.Global/Exceptions/CacheNotLoadedException.cs @@ -0,0 +1,14 @@ +namespace VNLib.Data.Caching.Global.Exceptions +{ + public class CacheNotLoadedException : GlobalCacheException + { + public CacheNotLoadedException() + { } + + public CacheNotLoadedException(string? message) : base(message) + { } + + public CacheNotLoadedException(string? message, Exception? innerException) : base(message, innerException) + { } + } +}
\ No newline at end of file diff --git a/VNLib.Data.Caching.Global/Exceptions/GlobalCacheException.cs b/VNLib.Data.Caching.Global/Exceptions/GlobalCacheException.cs new file mode 100644 index 0000000..89305a5 --- /dev/null +++ b/VNLib.Data.Caching.Global/Exceptions/GlobalCacheException.cs @@ -0,0 +1,12 @@ +namespace VNLib.Data.Caching.Global.Exceptions +{ + public class GlobalCacheException : Exception + { + public GlobalCacheException() + { } + public GlobalCacheException(string? message) : base(message) + { } + public GlobalCacheException(string? message, Exception? innerException) : base(message, innerException) + { } + } +}
\ No newline at end of file diff --git a/VNLib.Data.Caching.Global/GlobalDataCache.cs b/VNLib.Data.Caching.Global/GlobalDataCache.cs new file mode 100644 index 0000000..2c60ae2 --- /dev/null +++ b/VNLib.Data.Caching.Global/GlobalDataCache.cs @@ -0,0 +1,145 @@ +using VNLib.Data.Caching.Global.Exceptions; + +namespace VNLib.Data.Caching.Global +{ + /// <summary> + /// A static library for caching data in-process or a remote data + /// cache + /// </summary> + public static class GlobalDataCache + { + + private static IGlobalCacheProvider? CacheProvider; + private static CancellationTokenRegistration _reg; + + private static readonly object CacheLock = new(); + private static readonly Dictionary<string, WeakReference<object>> LocalCache = new(); + + /// <summary> + /// Gets a value that indicates if global cache is available + /// </summary> + public static bool IsAvailable => CacheProvider != null && CacheProvider.IsConnected; + + /// <summary> + /// Sets the backing cache provider for the process-wide global cache + /// </summary> + /// <param name="cacheProvider">The cache provider instance</param> + /// <param name="statusToken">A token that represents the store's validity</param> + public static void SetProvider(IGlobalCacheProvider cacheProvider, CancellationToken statusToken) + { + CacheProvider = cacheProvider ?? throw new ArgumentNullException(nameof(cacheProvider)); + //Remove cache provider when cache provider is no longer valid + _reg = statusToken.Register(Cleanup); + } + + private static void Cleanup() + { + CacheProvider = null; + //Clear local cache + lock (CacheLock) + { + LocalCache.Clear(); + } + _reg.Dispose(); + } + + /// <summary> + /// Asynchronously gets a value from the global cache provider + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key identifying the object to recover from cache</param> + /// <returns>The value if found, or null if it does not exist in the store</returns> + /// <exception cref="GlobalCacheException"></exception> + /// <exception cref="CacheNotLoadedException"></exception> + public static async Task<T?> GetAsync<T>(string key) where T: class + { + //Check local cache first + lock (CacheLock) + { + if (LocalCache.TryGetValue(key, out WeakReference<object>? wr)) + { + //Value is found + if(wr.TryGetTarget(out object? value)) + { + //Value exists and is loaded to local cache + return (T)value; + } + //Value has been collected + else + { + //Remove the key from the table + LocalCache.Remove(key); + } + } + } + //get ref to local cache provider + IGlobalCacheProvider? prov = CacheProvider; + if(prov == null) + { + throw new CacheNotLoadedException("Global cache provider was not found"); + } + //get the value from the store + T? val = await prov.GetAsync<T>(key); + //Only store the value if it was successfully found + if (val != null) + { + //Store in local cache + lock (CacheLock) + { + LocalCache[key] = new WeakReference<object>(val); + } + } + return val; + } + + /// <summary> + /// Asynchronously sets (or updates) a cached value in the global cache + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key identifying the object to recover from cache</param> + /// <param name="value">The value to set at the given key</param> + /// <returns>A task that completes when the update operation has compelted</returns> + /// <exception cref="GlobalCacheException"></exception> + /// <exception cref="CacheNotLoadedException"></exception> + public static async Task SetAsync<T>(string key, T value) where T : class + { + //Local record is stale, allow it to be loaded from cache next call to get + lock (CacheLock) + { + LocalCache.Remove(key); + } + //get ref to local cache provider + IGlobalCacheProvider? prov = CacheProvider; + if (prov == null) + { + throw new CacheNotLoadedException("Global cache provider was not found"); + } + //set the value in the store + await prov.SetAsync<T>(key, value); + } + + /// <summary> + /// Asynchronously deletes an item from cache by its key + /// </summary> + /// <param name="key"></param> + /// <returns>A task that completes when the delete operation has compelted</returns> + /// <exception cref="GlobalCacheException"></exception> + /// <exception cref="CacheNotLoadedException"></exception> + public static async Task DeleteAsync(string key) + { + //Delete from local cache + lock (CacheLock) + { + LocalCache.Remove(key); + } + //get ref to local cache provider + IGlobalCacheProvider? prov = CacheProvider; + if (prov == null) + { + throw new CacheNotLoadedException("Global cache provider was not found"); + } + //Delete value from store + await prov.DeleteAsync(key); + } + } +}
\ No newline at end of file diff --git a/VNLib.Data.Caching.Global/IGlobalCacheProvider.cs b/VNLib.Data.Caching.Global/IGlobalCacheProvider.cs new file mode 100644 index 0000000..12b4b0b --- /dev/null +++ b/VNLib.Data.Caching.Global/IGlobalCacheProvider.cs @@ -0,0 +1,39 @@ + +namespace VNLib.Data.Caching.Global +{ + /// <summary> + /// An interface that a cache provoider must impelement to provide data caching to the + /// <see cref="GlobalDataCache"/> environment + /// </summary> + public interface IGlobalCacheProvider + { + /// <summary> + /// Gets a value that indicates if the cache provider is currently available + /// </summary> + public bool IsConnected { get; } + + /// <summary> + /// Asynchronously gets a value from the backing cache store + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key identifying the object to recover from cache</param> + /// <returns>The value if found, or null if it does not exist in the store</returns> + Task<T?> GetAsync<T>(string key); + + /// <summary> + /// Asynchronously sets (or updates) a cached value in the backing cache store + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key identifying the object to recover from cache</param> + /// <param name="value">The value to set at the given key</param> + /// <returns>A task that completes when the update operation has compelted</returns> + Task SetAsync<T>(string key, T value); + + /// <summary> + /// Asynchronously deletes an item from cache by its key + /// </summary> + /// <param name="key">The key identifying the item to delete</param> + /// <returns>A task that completes when the delete operation has compelted</returns> + Task DeleteAsync(string key); + } +}
\ No newline at end of file diff --git a/VNLib.Data.Caching.Global/VNLib.Data.Caching.Global.csproj b/VNLib.Data.Caching.Global/VNLib.Data.Caching.Global.csproj new file mode 100644 index 0000000..0ec3c36 --- /dev/null +++ b/VNLib.Data.Caching.Global/VNLib.Data.Caching.Global.csproj @@ -0,0 +1,25 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <Authors>Vaughn Nugent</Authors> + <Copyright>Copyright © 2022 Vaughn Nugent</Copyright> + <Platforms>AnyCPU;ARM32;x64</Platforms> + <PlatformTarget>x64</PlatformTarget> + <GenerateDocumentationFile>True</GenerateDocumentationFile> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + </ItemGroup> + +</Project> |