diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs index 426984ab9f..0a9c79a80e 100644 --- a/src/Umbraco.Core/AsyncLock.cs +++ b/src/Umbraco.Core/AsyncLock.cs @@ -11,6 +11,15 @@ namespace Umbraco.Core // - this is NOT a reader/writer lock // - this is NOT a recursive lock // + // using a named Semaphore here and not a Mutex because mutexes have thread + // affinity which does not work with async situations + // + // it is important that managed code properly release the Semaphore before + // going down else it will maintain the lock - however note that when the + // whole process (w3wp.exe) goes down and all handles to the Semaphore have + // been closed, the Semaphore system object is destroyed - so in any case + // an iisreset should clean up everything + // internal class AsyncLock { private readonly SemaphoreSlim _semaphore; diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 16ffc828a9..6e00d3d682 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -66,7 +66,7 @@ namespace Umbraco.Core InitializeProfilerResolver(); - _timer = DisposableTimer.DebugDuration("Umbraco application starting", "Umbraco application startup complete"); + _timer = DisposableTimer.TraceDuration("Umbraco application starting", "Umbraco application startup complete"); CreateApplicationCache(); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs index fced0ce5d7..46b8eed789 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs @@ -130,7 +130,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } if (runNow) - Run(); + //Run(); + LogHelper.Warn("Cannot write now because we are going down, changes may be lost."); return ret; // this, by default, unless we created a new one } diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index 46ea9c1146..a11606937e 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -541,11 +541,13 @@ namespace Umbraco.Web.Scheduling { HostingEnvironment.UnregisterObject(this); LogHelper.Info>("Down, tasks completed."); + Stopped.RaiseEvent(new StoppedEventArgs(false), this); }); else { HostingEnvironment.UnregisterObject(this); LogHelper.Info>("Down, tasks completed."); + Stopped.RaiseEvent(new StoppedEventArgs(false), this); } } } @@ -560,9 +562,20 @@ namespace Umbraco.Web.Scheduling Shutdown(true, true); // cancel all tasks, wait for the current one to end HostingEnvironment.UnregisterObject(this); LogHelper.Info>("Down."); + Stopped.RaiseEvent(new StoppedEventArgs(true), this); } } - + public class StoppedEventArgs : EventArgs + { + public StoppedEventArgs(bool immediate) + { + Immediate = immediate; + } + + public bool Immediate { get; private set; } + } + + public event TypedEventHandler, StoppedEventArgs> Stopped; } } diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 64380b1484..34ecccd4ed 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -40,16 +40,13 @@ namespace umbraco private content() { - if (XmlFileEnabled) - { - // if we use the file, prepare the lock - InitializeFileLock(); - } - if (SyncToXmlFile) { - // if we write to file, prepare the persister task + // if we write to file, prepare the lock + // (if we don't use the file, or just read from it, no need to lock) + InitializeFileLock(); + // and prepare the persister task // there's always be one task keeping a ref to the runner // so it's safe to just create it as a local var here var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions @@ -58,6 +55,28 @@ namespace umbraco KeepAlive = true }); + // when the runner has stopped we know we will not be writing + // to the file anymore, so we can release the lock now - and + // not wait for the AppDomain unload + runner.Stopped += (sender, args) => + { + if (_fileLock == null) return; // not locking (testing?) + if (_fileLocked == null) return; // not locked + + // thread-safety + // lock something that's readonly and not null.. + lock (_xmlFileName) + { + // double-check + if (_fileLocked == null) return; + + LogHelper.Debug("Release file lock."); + _fileLocked.Dispose(); + _fileLocked = null; + _fileLock = null; // ensure we don't lock again + } + }; + // create (and add to runner) _persisterTask = new XmlCacheFilePersister(runner, this); } @@ -1019,8 +1038,8 @@ order by umbracoNode.level, umbracoNode.sortOrder"; private void OnDomainUnloadReleaseFileLock(object sender, EventArgs args) { // the unload event triggers AFTER all hosted objects (eg the file persister - // background task runner) have been stopped, so we should NOT be accessing - // the file from now one - release the lock + // background task runner) have been stopped, so we should have released the + // lock already - this is for safety - might be possible to get rid of it // NOTE // trying to write to the log via LogHelper at that point is a BAD idea @@ -1179,7 +1198,9 @@ order by umbracoNode.level, umbracoNode.sortOrder"; try { - EnsureFileLock(); + // if we're not writing back to the file, no need to lock + if (SyncToXmlFile) + EnsureFileLock(); var xml = new XmlDocument(); using (var fs = new FileStream(_xmlFileName, FileMode.Open, FileAccess.Read, FileShare.Read))