From c01f864e37de9f2b5b888c291a0ec8b375e988ec Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 3 Jul 2015 15:32:37 +0200 Subject: [PATCH] ApplicationContext - manage MainDom Conflicts: src/Umbraco.Core/ApplicationContext.cs src/Umbraco.Core/Umbraco.Core.csproj --- src/Umbraco.Core/ApplicationContext.cs | 27 ++-- src/Umbraco.Core/MainDom.cs | 182 +++++++++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 src/Umbraco.Core/MainDom.cs diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 0f160d5c83..eec91fe345 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -1,17 +1,13 @@ using System; using System.Configuration; using System.Threading; -using System.Web; -using System.Web.Caching; -using Umbraco.Core.Cache; +using System.Threading.Tasks; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Services; using Umbraco.Core.Sync; - namespace Umbraco.Core { /// @@ -65,13 +61,13 @@ namespace Umbraco.Core /// public static ApplicationContext EnsureContext(ApplicationContext appContext, bool replaceContext) { - if (ApplicationContext.Current != null) + if (Current != null) { if (!replaceContext) - return ApplicationContext.Current; + return Current; } - ApplicationContext.Current = appContext; - return ApplicationContext.Current; + Current = appContext; + return Current; } /// @@ -90,14 +86,14 @@ namespace Umbraco.Core /// public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, bool replaceContext) { - if (ApplicationContext.Current != null) + if (Current != null) { if (!replaceContext) - return ApplicationContext.Current; + return Current; } var ctx = new ApplicationContext(dbContext, serviceContext, cache); - ApplicationContext.Current = ctx; - return ApplicationContext.Current; + Current = ctx; + return Current; } /// @@ -196,9 +192,12 @@ namespace Umbraco.Core internal string _umbracoApplicationUrl; // internal for tests private Lazy _configured; - + internal MainDom MainDom { get; private set; } + private void Init() { + MainDom = new MainDom(); + MainDom.Acquire(); //Create the lazy value to resolve whether or not the application is 'configured' _configured = new Lazy(() => { diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs new file mode 100644 index 0000000000..f84af0743c --- /dev/null +++ b/src/Umbraco.Core/MainDom.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.MemoryMappedFiles; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Hosting; +using Umbraco.Core.Logging; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core +{ + // represents the main domain + class MainDom : IRegisteredObject + { + #region Vars + + // our own lock for local consistency + private readonly object _locko = new object(); + + // async lock representing the main domain lock + private readonly AsyncLock _asyncLock; + private IDisposable _asyncLocker; + + // event wait handle used to notify current main domain that it should + // release the lock because a new domain wants to be the main domain + private readonly EventWaitHandle _signal; + + // indicates whether... + private volatile bool _isMainDom; // we are the main domain + private volatile bool _signaled; // we have been signaled + + // actions to run before releasing the main domain + private readonly SortedList _callbacks = new SortedList(); + + private const int LockTimeoutMilliseconds = 4 * 60 * 1000; // 4' + + #endregion + + #region Ctor + + // initializes a new instance of MainDom + public MainDom() + { + var appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty); + + var lockName = "UMBRACO-" + appId + "-MAINDOM-LCK"; + _asyncLock = new AsyncLock(lockName); + + var eventName = "UMBRACO-" + appId + "-MAINDOM-EVT"; + _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + } + + #endregion + + // register a main domain consumer + public bool Register(Action release, int weight = 100) + { + return Register(null, release, weight); + } + + // register a main domain consumer + public bool Register(Action install, Action release, int weight = 100) + { + lock (_locko) + { + if (_signaled) return false; + if (install != null) + install(); + if (release != null) + _callbacks.Add(weight, release); + return true; + } + } + + // handles the signal requesting that the main domain is released + private void OnSignal(string source) + { + // once signaled, we stop waiting, but then there is the hosting environment + // so we have to make sure that we only enter that method once + + lock (_locko) + { + LogHelper.Debug("Signaled" + (_signaled ? " (again)" : "") + " (" + source + ")."); + if (_signaled) return; + if (_isMainDom == false) return; // probably not needed + _signaled = true; + } + + try + { + LogHelper.Debug("Stopping..."); + foreach (var callback in _callbacks.Values) + { + try + { + callback(); // no timeout on callbacks + } + catch (Exception e) + { + LogHelper.Error("Error while running callback, remaining callbacks will not run.", e); + throw; + } + + } + LogHelper.Debug("Stopped."); + } + finally + { + // in any case... + _isMainDom = false; + _asyncLocker.Dispose(); + LogHelper.Debug("Released MainDom."); + } + } + + // acquires the main domain + public bool Acquire() + { + lock (_locko) // we don't want the hosting environment to interfere by signaling + { + // if signaled, too late to acquire, give up + // the handler is not installed so that would be the hosting environment + if (_signaled) + { + LogHelper.Debug("Cannot acquire MainDom (signaled)."); + return false; + } + + LogHelper.Debug("Acquiring MainDom..."); + + // signal other instances that we want the lock, then wait one the lock, + // which may timeout, and this is accepted - see comments below + + // signal, then wait for the lock, then make sure the event is + // resetted (maybe there was noone listening..) + _signal.Set(); + + // if more than 1 instance reach that point, one will get the lock + // and the other one will timeout, which is accepted + + _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds); + _isMainDom = true; + + // we need to reset the event, because otherwise we would end up + // signaling ourselves and commiting suicide immediately. + // only 1 instance can reach that point, but other instances may + // have started and be trying to get the lock - they will timeout, + // which is accepted + + _signal.Reset(); + _signal.WaitOneAsync() + .ContinueWith(_ => OnSignal("signal")); + + HostingEnvironment.RegisterObject(this); + + LogHelper.Debug("Acquired MainDom."); + return true; + } + } + + // gets a value indicating whether we are the main domain + public bool IsMainDom + { + get { return _isMainDom; } + } + + // IRegisteredObject + public void Stop(bool immediate) + { + try + { + OnSignal("environment"); // will run once + } + finally + { + HostingEnvironment.UnregisterObject(this); + } + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5d5bb9807f..cf5b46776f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -317,6 +317,7 @@ +