/// <summary> /// Invoked by a Cache instance on updating, using properties from the PollNode such as connection strings, etc. /// </summary> /// <typeparam name="T">Type of item in the cache</typeparam> /// <param name="description">Description of the operation, used purely for profiling</param> /// <param name="getData">The operation used to actually get data, e.g. <code>using (var conn = GetConnectionAsync()) { return getFromConnection(conn); }</code></param> /// <param name="logExceptions">Whether to log any exceptions to the log</param> /// <param name="addExceptionData">Optionally add exception data, e.g. <code>e => e.AddLoggedData("Server", Name)</code></param> /// <returns>A cache update action, used when creating a <see cref="Cache"/>.</returns> protected Action <Cache <T> > UpdateCacheItem <T>(string description, Func <Task <T> > getData, bool logExceptions = false, // TODO: Settings Action <Exception> addExceptionData = null) where T : class { return(async cache => { if (OpserverProfileProvider.EnablePollerProfiling) { cache.Profiler = OpserverProfileProvider.CreateContextProfiler("Poll: " + description, cache.UniqueId); } using (MiniProfiler.Current.Step(description)) { CacheItemFetching?.Invoke(this, EventArgs.Empty); try { using (MiniProfiler.Current.Step("Data Fetch")) { cache.Data = await getData(); } cache.LastSuccess = cache.LastPoll = DateTime.UtcNow; cache.ErrorMessage = ""; PollFailsInaRow = 0; } catch (Exception e) { var deserializationException = e as DeserializationException; if (deserializationException != null) { e.AddLoggedData("Snippet-After", deserializationException.SnippetAfterError) .AddLoggedData("Position", deserializationException.Position.ToString()) .AddLoggedData("Ended-Unexpectedly", deserializationException.EndedUnexpectedly.ToString()); } if (logExceptions) { addExceptionData?.Invoke(e); Current.LogException(e); } cache.LastPoll = DateTime.UtcNow; PollFailsInaRow++; cache.ErrorMessage = "Unable to fetch from " + NodeType + ": " + e.Message; #if DEBUG cache.ErrorMessage += " @ " + e.StackTrace; #endif if (e.InnerException != null) { cache.ErrorMessage += "\n" + e.InnerException.Message; } } CacheItemFetched?.Invoke(this, EventArgs.Empty); CachedMonitorStatus = null; } if (OpserverProfileProvider.EnablePollerProfiling) { OpserverProfileProvider.StopContextProfiler(); } }); }
/// <summary> /// Invoked by a Cache instance on updating, using properties from the PollNode such as connection strings, etc. /// </summary> /// <typeparam name="T">Type of item in the cache</typeparam> /// <param name="description">Description of the operation, used purely for profiling</param> /// <param name="getData">The operation used to actually get data, e.g. <code>using (var conn = GetConnectionAsync()) { return getFromConnection(conn); }</code></param> /// <param name="logExceptions">Whether to log any exceptions to the log</param> /// <param name="addExceptionData">Optionally add exception data, e.g. <code>e => e.AddLoggedData("Server", Name)</code></param> /// <param name="timeoutMs">The timeout in milliseconds for this poll to complete before aborting.</param> /// <param name="afterPoll">An optional action to run after polling has completed successfully</param> /// <returns>A cache update action, used when creating a <see cref="Cache"/>.</returns> protected Func <Cache <T>, Task> UpdateCacheItem <T>(string description, Func <Task <T> > getData, bool logExceptions = false, // TODO: Settings Action <Exception> addExceptionData = null, int?timeoutMs = null, Action <Cache <T> > afterPoll = null) where T : class { return(async cache => { cache.PollStatus = "UpdateCacheItem"; if (OpserverProfileProvider.EnablePollerProfiling) { cache.Profiler = OpserverProfileProvider.CreateContextProfiler("Poll: " + description, cache.UniqueId, store: false); } using (MiniProfiler.Current.Step(description)) { CacheItemFetching?.Invoke(this, EventArgs.Empty); try { cache.PollStatus = "Fetching"; using (MiniProfiler.Current.Step("Data Fetch")) { var fetch = getData(); if (timeoutMs.HasValue) { if (await Task.WhenAny(fetch, Task.Delay(timeoutMs.Value)) == fetch) { // Re-await for throws. cache.SetData(await fetch.ConfigureAwait(false)); } else { throw new TimeoutException($"Fetch timed out after {timeoutMs.ToString()} ms."); } } else { cache.SetData(await fetch.ConfigureAwait(false)); } } cache.PollStatus = "Fetch Complete"; cache.SetSuccess(); PollFailsInaRow = 0; afterPoll?.Invoke(cache); } catch (Exception e) { if (logExceptions) { addExceptionData?.Invoke(e); Current.LogException(e); } PollFailsInaRow++; var errorMessage = "Unable to fetch from " + NodeType + ": " + e.Message; #if DEBUG errorMessage += " @ " + e.StackTrace; #endif if (e.InnerException != null) { errorMessage += "\n" + e.InnerException.Message; } cache.PollStatus = "Fetch Failed"; cache.SetFail(errorMessage); } CacheItemFetched?.Invoke(this, EventArgs.Empty); CachedMonitorStatus = null; LastFetch = cache; } if (OpserverProfileProvider.EnablePollerProfiling) { OpserverProfileProvider.StopContextProfiler(); } cache.PollStatus = "UpdateCacheItem Complete"; }); }
/// <summary> /// Invoked by a Cache instance on updating, using properties from the PollNode such as connection strings, etc. /// </summary> /// <typeparam name="T">Type of item in the cache</typeparam> /// <param name="description">Description of the operation, used purely for profiling</param> /// <param name="getData">The operation used to actually get data, e.g. <code>using (var conn = GetConnection()) { return getFromConnection(conn); }</code></param> /// <param name="logExceptions">Whether to log any exceptions to the log</param> /// <param name="addExceptionData">Optionally add exception data, e.g. <code>e => e.AddLoggedData("Server", Name)</code></param> /// <returns>A cache update action, used when creating a <see cref="Cache"/>.</returns> protected Action <Cache <T> > UpdateCacheItem <T>(string description, Func <T> getData, bool logExceptions = false, // TODO: Settings Action <Exception> addExceptionData = null) where T : class { return(cache => { if (OpserverProfileProvider.EnablePollerProfiling) { cache.Profiler = OpserverProfileProvider.CreateContextProfiler("Poll: " + description, cache.UniqueId); } using (MiniProfiler.Current.Step(description)) { if (CacheItemFetching != null) { CacheItemFetching(this, EventArgs.Empty); } try { using (MiniProfiler.Current.Step("Data Fetch")) { cache.Data = getData(); } cache.LastSuccess = cache.LastPoll = DateTime.UtcNow; cache.ErrorMessage = ""; PollFailsInaRow = 0; } catch (Exception e) { if (logExceptions) { if (addExceptionData != null) { addExceptionData(e); } Current.LogException(e); } cache.LastPoll = DateTime.UtcNow; PollFailsInaRow++; cache.ErrorMessage = "Unable to fetch from " + NodeType + ": " + e.Message; #if DEBUG cache.ErrorMessage += " @ " + e.StackTrace; #endif if (e.InnerException != null) { cache.ErrorMessage += "\n" + e.InnerException.Message; } } if (CacheItemFetched != null) { CacheItemFetched(this, EventArgs.Empty); } CachedMonitorStatus = null; } if (OpserverProfileProvider.EnablePollerProfiling) { OpserverProfileProvider.StopContextProfiler(); } }); }
/// <summary> /// Creates a cache poller /// </summary> /// <typeparam name="T">Type of item in the cache</typeparam> /// <param name="owner">The PollNode owner of this Cache</param> /// <param name="description">Description of the operation, used purely for profiling</param> /// <param name="cacheDuration">The length of time to cache data for</param> /// <param name="getData">The operation used to actually get data, e.g. <code>using (var conn = GetConnectionAsync()) { return getFromConnection(conn); }</code></param> /// <param name="timeoutMs">The timeout in milliseconds for this poll to complete before aborting.</param> /// <param name="logExceptions">Whether to log any exceptions to the log</param> /// <param name="addExceptionData">Optionally add exception data, e.g. <code>e => e.AddLoggedData("Server", Name)</code></param> /// <param name="afterPoll">An optional action to run after polling has completed successfully</param> /// <param name="memberName"></param> /// <param name="sourceFilePath"></param> /// <param name="sourceLineNumber"></param> /// <returns>A cache update action, used when creating a <see cref="Cache"/>.</returns> public Cache(PollNode owner, string description, TimeSpan cacheDuration, Func <Task <T> > getData, int?timeoutMs = null, bool logExceptions = false, // TODO: Settings Action <Exception> addExceptionData = null, Action <Cache <T> > afterPoll = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) : base(cacheDuration, memberName, sourceFilePath, sourceLineNumber) { MiniProfilerDescription = "Poll: " + description; // contcat once _updateFunc = async() => { var success = true; PollStatus = "UpdateCacheItem"; if (OpserverProfileProvider.EnablePollerProfiling) { Profiler = OpserverProfileProvider.CreateContextProfiler(MiniProfilerDescription, UniqueId, store: false); } using (MiniProfiler.Current.Step(description)) { try { PollStatus = "Fetching"; using (MiniProfiler.Current.Step("Data Fetch")) { var task = getData(); if (timeoutMs.HasValue) { if (await Task.WhenAny(task, Task.Delay(timeoutMs.Value)).ConfigureAwait(false) == task) { // Re-await for throws. Data = await task; } else { // This means the whenany returned the timeout first...boom. throw new TimeoutException($"Fetch timed out after {timeoutMs} ms."); } } else { Data = await task; } } PollStatus = "Fetch Complete"; SetSuccess(); afterPoll?.Invoke(this); } catch (Exception e) { success = false; if (logExceptions) { addExceptionData?.Invoke(e); Current.LogException(e); } var errorMessage = StringBuilderCache.Get() .Append("Unable to fetch from ") .Append(owner.NodeType) .Append(": ") .Append(e.Message); #if DEBUG errorMessage.Append(" @ ").Append(e.StackTrace); #endif if (e.InnerException != null) { errorMessage.AppendLine().Append(e.InnerException.Message); } PollStatus = "Fetch Failed"; SetFail(e, errorMessage.ToStringRecycle()); } owner.PollComplete(this, success); } if (OpserverProfileProvider.EnablePollerProfiling) { OpserverProfileProvider.StopContextProfiler(); } PollStatus = "UpdateCacheItem Complete"; return(Data); }; }