Use MainDom status to protect umbraco.config
Conflicts: src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlCacheFilePersister.cs src/Umbraco.Web/umbraco.presentation/content.cs
This commit is contained in:
@@ -37,7 +37,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
private const int MaxWaitMilliseconds = 30000; // save the cache after some time (ie no more than 30s of changes)
|
||||
|
||||
// save the cache when the app goes down
|
||||
public override bool RunsOnShutdown { get { return true; } }
|
||||
public override bool RunsOnShutdown { get { return _timer != null; } }
|
||||
|
||||
// initialize the first instance, which is inactive (not touched yet)
|
||||
public XmlCacheFilePersister(IBackgroundTaskRunner<XmlCacheFilePersister> runner, content content)
|
||||
@@ -140,13 +140,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
lock (_locko)
|
||||
{
|
||||
LogHelper.Debug<XmlCacheFilePersister>("Timer: release.");
|
||||
if (_timer != null)
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
_released = true;
|
||||
|
||||
// if running (because of shutdown) this will have no effect
|
||||
// else it tells the runner it is time to run the task
|
||||
Release();
|
||||
}
|
||||
}
|
||||
@@ -189,5 +184,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
_content.SaveXmlToFile();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
base.DisposeResources();
|
||||
|
||||
// stop the timer
|
||||
if (_timer == null) return;
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Hosting;
|
||||
using System.Xml;
|
||||
using umbraco.BusinessLogic;
|
||||
using umbraco.cms.businesslogic;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using umbraco.DataLayer;
|
||||
using umbraco.presentation.nodeFactory;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using umbraco.BusinessLogic;
|
||||
using umbraco.cms.businesslogic;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Profiling;
|
||||
using umbraco.DataLayer;
|
||||
using umbraco.presentation.nodeFactory;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache.XmlPublishedCache;
|
||||
using Umbraco.Web.Scheduling;
|
||||
using Node = umbraco.NodeFactory.Node;
|
||||
using File = System.IO.File;
|
||||
using Node = umbraco.NodeFactory.Node;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace umbraco
|
||||
{
|
||||
@@ -36,58 +35,44 @@ namespace umbraco
|
||||
{
|
||||
private XmlCacheFilePersister _persisterTask;
|
||||
|
||||
private volatile bool _released;
|
||||
|
||||
#region Constructors
|
||||
|
||||
private content()
|
||||
{
|
||||
if (SyncToXmlFile)
|
||||
{
|
||||
// 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
|
||||
// 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<XmlCacheFilePersister>("XmlCacheFilePersister", new BackgroundTaskRunnerOptions
|
||||
{
|
||||
LongRunning = true,
|
||||
KeepAlive = true
|
||||
KeepAlive = true,
|
||||
Hosted = false // main domain will take care of stopping the runner (see below)
|
||||
});
|
||||
|
||||
// when the runner is terminating we need to ensure that no modifications
|
||||
// to content are possible anymore, as they would not be written out to
|
||||
// the xml file - unfortunately that is not possible in 7.x because we
|
||||
// cannot lock the content service... and so we do nothing...
|
||||
//runner.Terminating += (sender, args) =>
|
||||
//{
|
||||
//};
|
||||
|
||||
// when the runner has terminated we know we will not be writing to the file
|
||||
// anymore, so we can release the lock now - no need to wait for the AppDomain
|
||||
// unload - which means any "last minute" saves will be lost - but waiting for
|
||||
// the AppDomain to unload has issues...
|
||||
runner.Terminated += (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<content>("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);
|
||||
|
||||
var registered = ApplicationContext.Current.MainDom.Register(
|
||||
null,
|
||||
() =>
|
||||
{
|
||||
// once released, the cache still works but does not write to file anymore,
|
||||
// which is OK with database server messenger but will cause data loss with
|
||||
// another messenger...
|
||||
|
||||
runner.Shutdown(false, true); // wait until flushed
|
||||
_released = true;
|
||||
});
|
||||
|
||||
// failed to become the main domain, we will never use the file
|
||||
if (registered == false)
|
||||
runner.Shutdown(false, true);
|
||||
|
||||
_released = (registered == false);
|
||||
}
|
||||
|
||||
// initialize content - populate the cache
|
||||
@@ -1004,100 +989,6 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
private readonly string _xmlFileName = IOHelper.MapPath(SystemFiles.ContentCacheXml);
|
||||
private DateTime _lastFileRead; // last time the file was read
|
||||
private DateTime _nextFileCheck; // last time we checked whether the file was changed
|
||||
private AsyncLock _fileLock; // protects the file
|
||||
private IDisposable _fileLocked; // protects the file
|
||||
|
||||
private const int FileLockTimeoutMilliseconds = 4*60*1000; // 4'
|
||||
|
||||
private void InitializeFileLock()
|
||||
{
|
||||
// initialize file lock
|
||||
// ApplicationId will look like "/LM/W3SVC/1/Root/AppName"
|
||||
// name is system-wide and must be less than 260 chars
|
||||
//
|
||||
// From MSDN C++ CreateSemaphore doc:
|
||||
// "The name can have a "Global\" or "Local\" prefix to explicitly create the object in
|
||||
// the global or session namespace. The remainder of the name can contain any character
|
||||
// except the backslash character (\). For more information, see Kernel Object Namespaces."
|
||||
//
|
||||
// From MSDN "Kernel object namespaces" doc:
|
||||
// "The separate client session namespaces enable multiple clients to run the same
|
||||
// applications without interfering with each other. For processes started under
|
||||
// a client session, the system uses the session namespace by default. However, these
|
||||
// processes can use the global namespace by prepending the "Global\" prefix to the object name."
|
||||
//
|
||||
// just use "default" (whatever it is) for now - ie, no prefix
|
||||
//
|
||||
var name = HostingEnvironment.ApplicationID + "/XmlStore/XmlFile";
|
||||
_fileLock = new AsyncLock(name);
|
||||
|
||||
// the file lock works with a shared, system-wide, semaphore - and we don't want
|
||||
// to leak a count on that semaphore else the whole process will hang - so we have
|
||||
// to ensure we dispose of the locker when the domain goes down - in theory the
|
||||
// async lock should do it via its finalizer, but then there are some weird cases
|
||||
// where the semaphore has been disposed of before it's been released, and then
|
||||
// we'd need to GC-pin the semaphore... better dispose the locker explicitely
|
||||
// when the app domain unloads.
|
||||
|
||||
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
|
||||
{
|
||||
LogHelper.Debug<content>("Registering Unload handler for default app domain.");
|
||||
AppDomain.CurrentDomain.ProcessExit += OnDomainUnloadReleaseFileLock;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Debug<content>("Registering Unload handler for non-default app domain.");
|
||||
AppDomain.CurrentDomain.DomainUnload += OnDomainUnloadReleaseFileLock;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureFileLock()
|
||||
{
|
||||
if (_fileLock == null) return; // not locking (testing?)
|
||||
if (_fileLocked != null) return; // locked already
|
||||
|
||||
// thread-safety, acquire lock only once!
|
||||
// lock something that's readonly and not null..
|
||||
lock (_xmlFileName)
|
||||
{
|
||||
// double-check
|
||||
if (_fileLock == null) return;
|
||||
if (_fileLocked != null) return;
|
||||
|
||||
// don't hang forever, throws if it cannot lock within the timeout
|
||||
LogHelper.Debug<content>("Acquiring exclusive access to file for this AppDomain...");
|
||||
_fileLocked = _fileLock.Lock(FileLockTimeoutMilliseconds);
|
||||
LogHelper.Debug<content>("Acquired exclusive access to file for this AppDomain.");
|
||||
}
|
||||
}
|
||||
|
||||
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 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
|
||||
// it can lead to ugly deadlocks with the named semaphore - DONT do it
|
||||
|
||||
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;
|
||||
|
||||
// in case you really need to debug... that should be safe...
|
||||
//System.IO.File.AppendAllText(HostingEnvironment.MapPath("~/App_Data/log.txt"), string.Format("{0} {1} unlock", DateTime.Now, AppDomain.CurrentDomain.Id));
|
||||
_fileLocked.Dispose();
|
||||
|
||||
_fileLock = null; // ensure we don't lock again
|
||||
}
|
||||
}
|
||||
|
||||
// not used - just try to read the file
|
||||
//private bool XmlFileExists
|
||||
@@ -1119,7 +1010,9 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
}
|
||||
}
|
||||
|
||||
// assumes file lock
|
||||
// invoked by XmlCacheFilePersister ONLY and that one manages the MainDom, ie it
|
||||
// will NOT try to save once the current app domain is not the main domain anymore
|
||||
// (no need to test _released)
|
||||
internal void SaveXmlToFile()
|
||||
{
|
||||
LogHelper.Info<content>("Save Xml to file...");
|
||||
@@ -1129,8 +1022,6 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
var xml = _xmlContent; // capture (atomic + volatile), immutable anyway
|
||||
if (xml == null) return;
|
||||
|
||||
EnsureFileLock();
|
||||
|
||||
// delete existing file, if any
|
||||
DeleteXmlFile();
|
||||
|
||||
@@ -1138,7 +1029,7 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
var directoryName = Path.GetDirectoryName(_xmlFileName);
|
||||
if (directoryName == null)
|
||||
throw new Exception(string.Format("Invalid XmlFileName \"{0}\".", _xmlFileName));
|
||||
if (System.IO.File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false)
|
||||
if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false)
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
// save
|
||||
@@ -1159,8 +1050,10 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
}
|
||||
}
|
||||
|
||||
// assumes file lock
|
||||
internal async System.Threading.Tasks.Task SaveXmlToFileAsync()
|
||||
// invoked by XmlCacheFilePersister ONLY and that one manages the MainDom, ie it
|
||||
// will NOT try to save once the current app domain is not the main domain anymore
|
||||
// (no need to test _released)
|
||||
internal async Task SaveXmlToFileAsync()
|
||||
{
|
||||
LogHelper.Info<content>("Save Xml to file...");
|
||||
|
||||
@@ -1169,8 +1062,6 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
var xml = _xmlContent; // capture (atomic + volatile), immutable anyway
|
||||
if (xml == null) return;
|
||||
|
||||
EnsureFileLock();
|
||||
|
||||
// delete existing file, if any
|
||||
DeleteXmlFile();
|
||||
|
||||
@@ -1178,7 +1069,7 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
var directoryName = Path.GetDirectoryName(_xmlFileName);
|
||||
if (directoryName == null)
|
||||
throw new Exception(string.Format("Invalid XmlFileName \"{0}\".", _xmlFileName));
|
||||
if (System.IO.File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false)
|
||||
if (File.Exists(_xmlFileName) == false && Directory.Exists(directoryName) == false)
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
// save
|
||||
@@ -1227,17 +1118,15 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// assumes file lock
|
||||
private XmlDocument LoadXmlFromFile()
|
||||
{
|
||||
// do NOT try to load if we are not the main domain anymore
|
||||
if (_released) return null;
|
||||
|
||||
LogHelper.Info<content>("Load Xml from file...");
|
||||
|
||||
try
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
@@ -1260,12 +1149,11 @@ order by umbracoNode.level, umbracoNode.sortOrder";
|
||||
}
|
||||
}
|
||||
|
||||
// (files is always locked)
|
||||
private void DeleteXmlFile()
|
||||
{
|
||||
if (System.IO.File.Exists(_xmlFileName) == false) return;
|
||||
System.IO.File.SetAttributes(_xmlFileName, FileAttributes.Normal);
|
||||
System.IO.File.Delete(_xmlFileName);
|
||||
if (File.Exists(_xmlFileName) == false) return;
|
||||
File.SetAttributes(_xmlFileName, FileAttributes.Normal);
|
||||
File.Delete(_xmlFileName);
|
||||
}
|
||||
|
||||
private void ReloadXmlFromFileIfChanged()
|
||||
|
||||
Reference in New Issue
Block a user