protected void Application_Start() { // disable the X-AspNetMvc-Version: header MvcHandler.DisableMvcResponseHeader = true; AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); RegisterBundles(BundleTable.Bundles); //BundleTable.EnableOptimizations = true; SetupMiniProfiler(); Exceptional.Exceptional.Settings.GetCustomData = GetCustomErrorData; TaskScheduler.UnobservedTaskException += (sender, args) => Current.LogException(args.Exception); // enable custom model binder ModelBinders.Binders.DefaultBinder = new ProfiledModelBinder(); // When settings change, reload the app pool Current.Settings.OnChanged += HttpRuntime.UnloadAppDomain; PollingEngine.Configure(t => HostingEnvironment.QueueBackgroundWorkItem(_ => t())); }
/// <summary> /// /// lookup refreshes the data if necessary, passing the old data if we have it. /// /// durationSecs is the "time before stale" for the data /// serveStaleSecs is the maximum amount of time to serve data once it becomes stale /// /// Note that one unlucky caller when the data is stale will block to fill the cache, /// everybody else will get stale data though. /// </summary> public static T GetSet <T>(this LocalCache cache, string key, Func <T, MicroContext, T> lookup, int durationSecs, int serveStaleDataSecs) where T : class { var possiblyStale = cache.Get <GetSetWrapper <T> >(key); var localLockName = key; var nullLoadLock = _getSetNullLocks.AddOrUpdate(localLockName, k => new object(), (k, old) => old); if (possiblyStale == null) { // We can't prevent multiple web server's from running this (well, we can but its probably overkill) but we can // at least stop the query from running multiple times on *this* web server lock (nullLoadLock) { possiblyStale = cache.Get <GetSetWrapper <T> >(key); if (possiblyStale == null) { T data; using (var ctx = new MicroContext()) { data = lookup(null, ctx); } possiblyStale = new GetSetWrapper <T> { Data = data, StaleAfter = DateTime.UtcNow + TimeSpan.FromSeconds(durationSecs) }; cache.Set(key, possiblyStale, durationSecs + serveStaleDataSecs); Interlocked.Increment(ref totalGetSetSync); } } } if (possiblyStale.StaleAfter > DateTime.UtcNow) { return(possiblyStale.Data); } bool gotCompeteLock = false; if (Monitor.TryEnter(nullLoadLock, 0)) { // it isn't actively being refreshed; we'll check for a mutex on the cache try { gotCompeteLock = GotCompeteLock(cache, key); } finally { Monitor.Exit(nullLoadLock); } } if (gotCompeteLock) { var old = possiblyStale.Data; var task = new Task(delegate { lock (nullLoadLock) // holding this lock allows us to locally short-circuit all the other threads that come asking { try { var updated = new GetSetWrapper <T>(); using (var ctx = new MicroContext()) { updated.Data = lookup(old, ctx); updated.StaleAfter = DateTime.UtcNow + TimeSpan.FromSeconds(durationSecs); } cache.Remove(key); cache.Set(key, updated, durationSecs + serveStaleDataSecs); } finally { ReleaseCompeteLock(cache, key); } } }); task.ContinueWith(t => { if (t.IsFaulted) { Interlocked.Increment(ref totalGetSetAsyncError); Current.LogException(t.Exception); } else { Interlocked.Increment(ref totalGetSetAsyncSuccess); } }); task.Start(); } return(possiblyStale.Data); }
/// <summary> /// Updates this settings object, return true if there was an actual change /// </summary> public virtual bool UpdateSettings(T newSettings) { //Current.LogException("Settings updated! " + GetType(), null); if (newSettings == null) { //Current.LogException("Updated settings for " + typeof(T).Name + " were null, aborting.", null); return(false); } bool changed = false; try { var toCopy = Properties; foreach (var prop in toCopy) { if (!prop.CanWrite) { continue; } var current = prop.GetValue(this); var newSetting = prop.GetValue(newSettings); // observables are meant to be updated at the member level and need specific love if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(ObservableCollection <>)) { // handle collections! // TODO: Decide on how collections (and subcollections) are handled // need to broadcast these changes or publish them via json APIs } else if (prop.CanWrite) { try { if (current == newSetting) { continue; // nothing changed, NEXT } prop.SetValue(this, newSetting); OnPropertyChanged(prop.Name); changed = true; } catch (Exception e) { Current.LogException("Error setting propery: " + prop.Name, e); } } } } catch (Exception e) { Current.LogException("Error updating settings for " + typeof(T).Name, e); } if (changed) { TriggerChanged(); } return(changed); }