using VNLib.Data.Caching.Global.Exceptions; namespace VNLib.Data.Caching.Global { /// /// A static library for caching data in-process or a remote data /// cache /// public static class GlobalDataCache { private static IGlobalCacheProvider? CacheProvider; private static CancellationTokenRegistration _reg; private static readonly object CacheLock = new(); private static readonly Dictionary> LocalCache = new(); /// /// Gets a value that indicates if global cache is available /// public static bool IsAvailable => CacheProvider != null && CacheProvider.IsConnected; /// /// Sets the backing cache provider for the process-wide global cache /// /// The cache provider instance /// A token that represents the store's validity 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(); } /// /// Asynchronously gets a value from the global cache provider /// /// /// The key identifying the object to recover from cache /// The value if found, or null if it does not exist in the store /// /// public static async Task GetAsync(string key) where T: class { //Check local cache first lock (CacheLock) { if (LocalCache.TryGetValue(key, out WeakReference? 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(key); //Only store the value if it was successfully found if (val != null) { //Store in local cache lock (CacheLock) { LocalCache[key] = new WeakReference(val); } } return val; } /// /// Asynchronously sets (or updates) a cached value in the global cache /// /// /// The key identifying the object to recover from cache /// The value to set at the given key /// A task that completes when the update operation has compelted /// /// public static async Task SetAsync(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(key, value); } /// /// Asynchronously deletes an item from cache by its key /// /// /// A task that completes when the delete operation has compelted /// /// 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); } } }