/// <summary> /// Acquires a system-wide lock on the shared cache /// </summary> /// <param name="timeoutMs">Max milliseconds to wait for lock</param> /// <returns>True if the lock was acquired, False if the timeout was reached without acquiring the lock</returns> public bool Lock(int?timeoutMs = null) { try { if (cacheLock.Value == null) { return(false); } if (timeoutMs.HasValue) { return(cacheLock.Value.WaitOne(timeoutMs.Value)); } return(cacheLock.Value.WaitOne()); } catch (AbandonedMutexException) { return(true); } catch (Exception exception) { string eventName = "VS/Core/TargetedNotifications/MutexFailure"; telemetry.PostCriticalFault(eventName, "Failed to lock Mutex", exception); return(false); } }
public TargetedNotificationsJsonStorageProvider(RemoteSettingsInitializer initializer) { cacheDirectory = initializer.GetLocalAppDataRoot(); cacheFileFullPath = Path.Combine(cacheDirectory, "targetnote_v1.json"); telemetry = initializer.TargetedNotificationsTelemetry; cacheLock = new Lazy <Mutex>(delegate { try { return(new Mutex(false, "Global\\55F58BAB-BDB9-47D5-B85E-B4D8234E8FAA")); } catch (Exception exception) { string eventName = "VS/Core/TargetedNotifications/MutexFailure"; telemetry.PostCriticalFault(eventName, "Failed to create Mutex", exception); return(null); } }); }
private void ProcessActionResponseBag(ActionResponseBag response) { logger.LogInfo($"Received {response.Actions.Count()} actions from {Name}"); List <string> list; try { actionsAndCategoriesLock.Wait(); list = tnActions.Values.SelectMany((Dictionary <string, ActionResponse> x) => x.Keys).ToList(); foreach (ActionResponse action in response.Actions) { if (!tnActions.TryGetValue(action.ActionPath, out Dictionary <string, ActionResponse> value)) { value = new Dictionary <string, ActionResponse>(StringComparer.OrdinalIgnoreCase); tnActions[action.ActionPath] = value; } if (action.RuleId != null) { value[action.RuleId] = action; } else { logger.LogInfo("Skipping action that has no RuleId " + action.ActionPath + " from " + Name); } } foreach (ActionCategory category in response.Categories) { ActionCategories[category.CategoryId] = category; } } finally { actionsAndCategoriesLock.Release(); } lock (subscriptionLockObject) { try { foreach (string key in tnSubscriptionCallbacks.Keys) { foreach (Type key2 in tnSubscriptionCallbacks[key].Keys) { MethodInfo methodInfo = typeof(TargetedNotificationsProviderBase).GetMethod("SubscribeActionsInternal", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(key2); foreach (object item in tnSubscriptionCallbacks[key][key2]) { methodInfo.Invoke(this, new object[3] { key, item, list }); } } } } catch (Exception exception) { string eventName = "VS/Core/TargetedNotifications/ReSubscribeFailure"; targetedNotificationsTelemetry.PostCriticalFault(eventName, "failure", exception); } } string eventName2 = "VS/Core/TargetedNotifications/ActionsReceived"; IEnumerable <ActionResponse> source = response.Actions ?? Enumerable.Empty <ActionResponse>(); Dictionary <string, IEnumerable <string> > val = (from x in source group x by x.ActionPath).ToDictionary((IGrouping <string, ActionResponse> x) => x.Key, (IGrouping <string, ActionResponse> x) => x.Select((ActionResponse y) => y.RuleId)); List <string> list2 = ActionCategories.Keys.ToList(); Dictionary <string, object> additionalProperties = new Dictionary <string, object> { { "VS.Core.TargetedNotifications.ProviderName", Name }, { "VS.Core.TargetedNotifications.Actions", new TelemetryComplexProperty(val) }, { "VS.Core.TargetedNotifications.ActionCount", source.Count() }, { "VS.Core.TargetedNotifications.Categories", new TelemetryComplexProperty(list2) }, { "VS.Core.TargetedNotifications.CategoryCount", list2.Count }, { "VS.Core.TargetedNotifications.ApiResponseMs", apiTimer?.ElapsedMilliseconds }, { "VS.Core.TargetedNotifications.Iteration", queryIteration } }; targetedNotificationsTelemetry.PostSuccessfulOperation(eventName2, additionalProperties); }
/// <summary> /// Called when a new ActionResponseBag is received from the /// service. This merges that new data into the existing cache /// </summary> /// <param name="newResponse">An ActionResponseBag received from the Azure API</param> /// <param name="previouslyCachedRuleIds">Set of rule IDs that were previously read from the cache and should not be written back</param> /// <param name="timeoutMs">Maximum time to wait, in milliseconds, for the cache lock. Leave null for infinite.</param> public void MergeNewResponse(ActionResponseBag newResponse, IEnumerable <string> previouslyCachedRuleIds, int?timeoutMs = null) { if (Storage.Lock(timeoutMs)) { Stopwatch stopwatch = Stopwatch.StartNew(); try { DateTime utcNow = DateTime.UtcNow; bool flag = false; CachedTargetedNotifications localCacheCopy = Storage.GetLocalCacheCopy(); List <string> list = new List <string>(); List <string> list2 = new List <string>(); foreach (ActionResponse action in newResponse.Actions) { if (action.SendAlways) { if (localCacheCopy.Actions.Remove(action.RuleId)) { flag = true; } } else if (!previouslyCachedRuleIds.Contains(action.RuleId)) { localCacheCopy.Actions[action.RuleId] = new CachedActionResponseTime { CachedTime = (localCacheCopy.Actions.ContainsKey(action.RuleId) ? localCacheCopy.Actions[action.RuleId].CachedTime : utcNow), MaxWaitTimeSpan = ((action.MaxWaitTimeSpan == null) ? defaultMaxWaitTimeSpan : TimeSpan.Parse(action.MaxWaitTimeSpan)) }; list.Add(action.RuleId); flag = true; } } string[] array = localCacheCopy.Actions.Keys.ToArray(); foreach (string text in array) { CachedActionResponseTime cachedActionResponseTime = localCacheCopy.Actions[text]; if (utcNow >= cachedActionResponseTime.CachedTime.Add(cachedActionResponseTime.MaxWaitTimeSpan)) { localCacheCopy.Actions.Remove(text); list2.Add(text); flag = true; } } foreach (ActionCategory category in newResponse.Categories) { if (localCacheCopy.Categories.ContainsKey(category.CategoryId)) { TimeSpan timeSpan = TimeSpan.Parse(category.WaitTimeSpan); if (localCacheCopy.Categories[category.CategoryId].WaitTimeSpan != timeSpan) { localCacheCopy.Categories[category.CategoryId].WaitTimeSpan = timeSpan; flag = true; } } } array = localCacheCopy.Categories.Keys.ToArray(); foreach (string key in array) { CachedActionCategoryTime cachedActionCategoryTime = localCacheCopy.Categories[key]; if (utcNow >= cachedActionCategoryTime.LastSent.Add(cachedActionCategoryTime.WaitTimeSpan)) { localCacheCopy.Categories.Remove(key); flag = true; } } if (flag) { Storage.SetLocalCache(localCacheCopy); } if (list.Count > 0) { responseUsesCachedRules = true; } stopwatch.Stop(); if (list.Count > 0 || list2.Count > 0 || localCacheCopy.Actions.Count > 0 || localCacheCopy.Categories.Count > 0) { string eventName = "VS/Core/TargetedNotifications/CacheMerged"; Dictionary <string, object> additionalProperties = new Dictionary <string, object> { { "VS.Core.TargetedNotifications.AddedActions", new TelemetryComplexProperty(list) }, { "VS.Core.TargetedNotifications.AddedActionsCount", list.Count }, { "VS.Core.TargetedNotifications.ExpiredActions", new TelemetryComplexProperty(list2) }, { "VS.Core.TargetedNotifications.ExpiredActionsCount", list2.Count }, { "VS.Core.TargetedNotifications.CachedActions", new TelemetryComplexProperty(localCacheCopy.Actions.Keys.ToList()) }, { "VS.Core.TargetedNotifications.CachedActionsCount", localCacheCopy.Actions.Count }, { "VS.Core.TargetedNotifications.CachedCategories", new TelemetryComplexProperty(localCacheCopy.Categories.Keys.ToList()) }, { "VS.Core.TargetedNotifications.CachedCategoriesCount", localCacheCopy.Categories.Count }, { "VS.Core.TargetedNotifications.DurationMs", stopwatch.ElapsedMilliseconds } }; telemetry.PostSuccessfulOperation(eventName, additionalProperties); } } catch (Exception exception) { stopwatch.Stop(); string eventName2 = "VS/Core/TargetedNotifications/CacheMergeFailure"; string[] array2 = (from a in newResponse.Actions where !a.SendAlways select a.RuleId).Except(previouslyCachedRuleIds).ToArray(); Dictionary <string, object> additionalProperties2 = new Dictionary <string, object> { { "VS.Core.TargetedNotifications.SendOnceActions", new TelemetryComplexProperty(array2) }, { "VS.Core.TargetedNotifications.SendOnceActionsCount", array2.Length }, { "VS.Core.TargetedNotifications.DurationMs", stopwatch.ElapsedMilliseconds } }; telemetry.PostCriticalFault(eventName2, "Failed to merge new response with cache", exception, additionalProperties2); } finally { Storage.Unlock(); } } else { string eventName3 = "VS/Core/TargetedNotifications/CacheLockTimeout"; Dictionary <string, object> additionalProperties3 = new Dictionary <string, object> { { "VS.Core.TargetedNotifications.Operation", "MergeNewResponse" }, { "VS.Core.TargetedNotifications.TimeoutMs", timeoutMs } }; telemetry.PostDiagnosticFault(eventName3, "Timeout acquiring cache lock", null, additionalProperties3); } }