public object Memoize(object dataSource, MethodInfo method, object[] args, CacheItemPolicyEx policy) { if (dataSource == null) { throw new ArgumentNullException(nameof(dataSource)); } if (method == null) { throw new ArgumentNullException(nameof(method)); } if (args == null) { throw new ArgumentNullException(nameof(args)); } var taskResultType = MetadataProvider.GetMethodTaskResultType(method); if (taskResultType == null) { throw new ArgumentException("The specified method doesn't return Task<T> and therefore cannot be memoized", nameof(method)); } var target = new InvocationTarget(method, method.GetParameters()); string cacheKey = $"{target}#{GetArgumentHash(args)}"; return(Cache.GetOrAdd(cacheKey, () => (Task)method.Invoke(dataSource, args), taskResultType, policy, target.MethodName, string.Join(",", args), new [] { target.TypeName, target.MethodName })); }
private Task <object> GetOrAdd(string key, Func <Task <object> > factory, CacheItemPolicyEx policy, string groupName, string logData, string[] metricsKeys, Type taskResultType) { var shouldLog = ShouldLog(groupName); async Task <object> WrappedFactory(bool removeOnException) { try { if (shouldLog) { Log.Info(x => x("Cache item is waiting for value to be resolved", unencryptedTags: new { cacheKey = key, cacheGroup = groupName, cacheData = logData })); } var result = await factory().ConfigureAwait(false); if (shouldLog) { Log.Info(x => x("Cache item value is resolved", unencryptedTags: new { cacheKey = key, cacheGroup = groupName, cacheData = logData, value = GetValueForLogging(result) })); } //Can happen if item removed before task is completed if (MemoryCache.Contains(key)) { var revocableResult = result as IRevocable; if (revocableResult?.RevokeKeys != null) { foreach (var revokeKey in revocableResult.RevokeKeys) { var cacheKeys = RevokeKeyToCacheKeysIndex.GetOrAdd(revokeKey, k => new HashSet <string>()); lock (cacheKeys) { cacheKeys.Add(key); } Log.Info(x => x("RevokeKey added to reverse index", unencryptedTags: new { revokeKey = revokeKey, cacheKey = key, cacheGroup = groupName, cacheData = logData })); } } } return(result); } catch (Exception exception) { Log.Info(x => x("Error resolving value for cache item", unencryptedTags: new { cacheKey = key, cacheGroup = groupName, cacheData = logData, removeOnException, errorMessage = exception.Message })); if (removeOnException) { MemoryCache.Remove(key); // Do not cache exceptions. } throw; } } var newItem = shouldLog ? new AsyncCacheItem { GroupName = string.Intern(groupName), LogData = logData } : new AsyncCacheItem(); // if log is not needed, then do not cache unnecessary details which will blow up the memory Task <object> resultTask; // Taking a lock on the newItem in case it actually becomes the item in the cache (if no item with that key // existed). For another thread, it will be returned into the existingItem variable and will block on the // second lock, preventing concurrent mutation of the same object. lock (newItem.Lock) { if (typeof(IRevocable).IsAssignableFrom(taskResultType)) { policy.RemovedCallback += ItemRemovedCallback; } // Surprisingly, when using MemoryCache.AddOrGetExisting() where the item doesn't exist in the cache, // null is returned. var existingItem = (AsyncCacheItem)MemoryCache.AddOrGetExisting(key, newItem, policy); if (existingItem == null) { newItem.CurrentValueTask = WrappedFactory(true); newItem.NextRefreshTime = DateTime.UtcNow + policy.RefreshTime; resultTask = newItem.CurrentValueTask; if (shouldLog) { Log.Info(x => x("Item added to cache", unencryptedTags: new { cacheKey = key, cacheGroup = groupName, cacheData = logData })); } } else { // This lock makes sure we're not mutating the same object as was added to the cache by an earlier // thread (which was the first to add from 'newItem', for subsequent threads it will be 'existingItem'). lock (existingItem.Lock) { resultTask = existingItem.CurrentValueTask; // Start refresh if an existing refresh ins't in progress and we've passed the next refresh time. if (existingItem.RefreshTask?.IsCompleted != false && DateTime.UtcNow >= existingItem.NextRefreshTime) { existingItem.RefreshTask = ((Func <Task>)(async() => { try { var getNewValue = WrappedFactory(false); await getNewValue.ConfigureAwait(false); existingItem.CurrentValueTask = getNewValue; existingItem.NextRefreshTime = DateTime.UtcNow + policy.RefreshTime; MemoryCache.Set(new CacheItem(key, existingItem), policy); } catch { existingItem.NextRefreshTime = DateTime.UtcNow + policy.FailedRefreshDelay; } })).Invoke(); } } } } return(resultTask); }
public Task GetOrAdd(string key, Func <Task> factory, Type taskResultType, CacheItemPolicyEx policy, string groupName, string logData, string[] metricsKeys) { var getValueTask = GetOrAdd(key, () => TaskConverter.ToWeaklyTypedTask(factory(), taskResultType), policy, groupName, logData, metricsKeys, taskResultType); return(TaskConverter.ToStronglyTypedTask(getValueTask, taskResultType)); }