/* * 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 { /// /// Represents a cache that can store entities by their unique key /// using a user-provided backing store and custom request state. /// /// /// The cache backing store /// Specifies how background cache tasks are handled /// The result expiration policy public class EntityResultCache( IEntityCache cache, ICacheTaskPolicy taskPolicy, ICacheExpirationPolicy expirationPolicy ) where TEntity : class { /// /// Fetchs a result by it's request entity /// /// The fetch request state object /// A token to canel the operation /// A callback generator function /// A task the returns the result of the requested entity, or null if it was not found or provided by the backing store public async Task FetchAsync( TRequest request, Func> 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; } /// /// Removes an entity from the cache by it's request entity /// /// The request entity to retrieve the entity key from /// A token to cancel the async operation /// A task that completes when the key is removed, based on the task policy public Task RemoveAsync(TRequest request, CancellationToken cancellation = default) where TRequest : IEntityCacheKey { ArgumentNullException.ThrowIfNull(request); string key = request.GetKey(); return cache.RemoveAsync(key, cancellation); } /// /// Removes an entity from the cache by it's request entity /// /// The entities unique key /// A token to cancel the async operation /// A task that completes when the key is removed, based on the task policy public Task RemoveAsync(string key, CancellationToken cancellation = default) { ArgumentException.ThrowIfNullOrWhiteSpace(key); Task remove = cache.RemoveAsync(key, cancellation); return taskPolicy.ObserveOperationAsync(remove); } } }