aboutsummaryrefslogtreecommitdiff
path: root/VNLib.Data.Caching.Global
diff options
context:
space:
mode:
Diffstat (limited to 'VNLib.Data.Caching.Global')
-rw-r--r--VNLib.Data.Caching.Global/Exceptions/CacheNotLoadedException.cs14
-rw-r--r--VNLib.Data.Caching.Global/Exceptions/GlobalCacheException.cs12
-rw-r--r--VNLib.Data.Caching.Global/GlobalDataCache.cs145
-rw-r--r--VNLib.Data.Caching.Global/IGlobalCacheProvider.cs39
-rw-r--r--VNLib.Data.Caching.Global/VNLib.Data.Caching.Global.csproj25
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>