private async Task <(Task <object> response, ResponseKinds responseKind)> CallService(Func <Task <object> > serviceMethod, string[] metricsKeys) { Task <object> response; ResponseKinds responseKind; try { AwaitingResult.Increment(metricsKeys); // Call the service response = Task.FromResult(await serviceMethod()); // Determine the kind of response. We consider it null also in case we got a non-null Revocable<> with null inner Value var isNullResponse = response.Result == null || (response.Result.GetType().IsGenericType && response.Result.GetType().GetGenericTypeDefinition() == typeof(Revocable <>) && response.Result.GetType().GetProperty("Value").GetValue(response.Result) == null); responseKind = isNullResponse ? ResponseKinds.NullResponse : ResponseKinds.NonNullResponse; } catch (Exception e) { if (!(e is RequestException)) { Failed.Mark(metricsKeys); } // Determine the kind of exception responseKind = e is RequestException ? ResponseKinds.RequestException : e is EnvironmentException ? ResponseKinds.EnvironmentException : e is TimeoutException || e is TaskCanceledException ? ResponseKinds.TimeoutException : ResponseKinds.OtherExceptions; response = Task.FromException <object>(e); var observed = response.Exception; //dont remove this line as it prevents UnobservedTaskException to be thrown } finally { AwaitingResult.Decrement(metricsKeys); } return(response, responseKind); }
private Task <object> GetOrAdd(string key, Func <Task <object> > factory, CacheItemPolicyEx policy, string[] metricsKeys, Type taskResultType) { Func <bool, Task <object> > wrappedFactory = async removeOnException => { try { var result = await factory().ConfigureAwait(false); //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); } } } } AwaitingResult.Decrement(metricsKeys); return(result); } catch { if (removeOnException) { MemoryCache.Remove(key); // Do not cache exceptions. } AwaitingResult.Decrement(metricsKeys); Failed.Mark(metricsKeys); throw; } }; var newItem = new AsyncCacheItem(); 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) { Misses.Mark(metricsKeys); AwaitingResult.Increment(metricsKeys); newItem.CurrentValueTask = wrappedFactory(true); newItem.NextRefreshTime = DateTime.UtcNow + policy.RefreshTime; resultTask = newItem.CurrentValueTask; } 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 == null && 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; existingItem.RefreshTask = null; MemoryCache.Set(new CacheItem(key, existingItem), policy); } catch { existingItem.NextRefreshTime = DateTime.UtcNow + policy.FailedRefreshDelay; existingItem.RefreshTask = null; } })).Invoke(); } } if (resultTask.GetAwaiter().IsCompleted) { Hits.Mark(metricsKeys); } else { JoinedTeam.Mark(metricsKeys); } } } return(resultTask); }
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 })); } } } AwaitingResult.Decrement(metricsKeys); 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. } AwaitingResult.Decrement(metricsKeys); Failed.Mark(metricsKeys); 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) { Misses.Mark(metricsKeys); AwaitingResult.Increment(metricsKeys); 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(); } } if (resultTask.GetAwaiter().IsCompleted) { Hits.Mark(metricsKeys); } else { JoinedTeam.Mark(metricsKeys); } } } return(resultTask); }