diff --git a/src/Umbraco.Web/Routing/DefaultRoutesCache.cs b/src/Umbraco.Web/Routing/DefaultRoutesCache.cs index 5a454af821..29ecb8ac58 100644 --- a/src/Umbraco.Web/Routing/DefaultRoutesCache.cs +++ b/src/Umbraco.Web/Routing/DefaultRoutesCache.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Umbraco.Core; +using Umbraco.Core.ObjectResolution; namespace Umbraco.Web.Routing { @@ -28,34 +29,44 @@ namespace Umbraco.Web.Routing if (bindToEvents) { - // content - whenever the entire XML cache is rebuilt (from disk cache or from database) - // we must clear the cache entirely - global::umbraco.content.AfterRefreshContent += (sender, e) => Clear(); - - // document - whenever a document is updated in, or removed from, the XML cache - // we must clear the cache - at the moment, we clear the entire cache - // TODO could we do partial updates instead of clearing the whole cache? - global::umbraco.content.AfterUpdateDocumentCache += (sender, e) => Clear(); - global::umbraco.content.AfterClearDocumentCache += (sender, e) => Clear(); - - // domains - whenever a domain change we must clear the cache - // because routes contain the id of root nodes of domains - // TODO could we do partial updates instead of clearing the whole cache? - global::umbraco.cms.businesslogic.web.Domain.AfterDelete += (sender, e) => Clear(); - global::umbraco.cms.businesslogic.web.Domain.AfterSave += (sender, e) => Clear(); - global::umbraco.cms.businesslogic.web.Domain.New += (sender, e) => Clear(); - - // FIXME - // the content class needs to be refactored - at the moment - // content.XmlContentInternal setter does not trigger any event - // content.UpdateDocumentCache(List Documents) does not trigger any event - // content.RefreshContentFromDatabaseAsync triggers AfterRefresh _while_ refreshing - // etc... - // in addition some events do not make sense... we trigger Publish when moving - // a node, which we should not (the node is moved, not published...) etc. + Resolution.Frozen += ResolutionFrozen; } } + /// + /// Once resolution is frozen, then we can bind to the events that we require + /// + /// + /// + void ResolutionFrozen(object s, EventArgs args) + { + // content - whenever the entire XML cache is rebuilt (from disk cache or from database) + // we must clear the cache entirely + global::umbraco.content.AfterRefreshContent += (sender, e) => Clear(); + + // document - whenever a document is updated in, or removed from, the XML cache + // we must clear the cache - at the moment, we clear the entire cache + // TODO could we do partial updates instead of clearing the whole cache? + global::umbraco.content.AfterUpdateDocumentCache += (sender, e) => Clear(); + global::umbraco.content.AfterClearDocumentCache += (sender, e) => Clear(); + + // domains - whenever a domain change we must clear the cache + // because routes contain the id of root nodes of domains + // TODO could we do partial updates instead of clearing the whole cache? + global::umbraco.cms.businesslogic.web.Domain.AfterDelete += (sender, e) => Clear(); + global::umbraco.cms.businesslogic.web.Domain.AfterSave += (sender, e) => Clear(); + global::umbraco.cms.businesslogic.web.Domain.New += (sender, e) => Clear(); + + // FIXME + // the content class needs to be refactored - at the moment + // content.XmlContentInternal setter does not trigger any event + // content.UpdateDocumentCache(List Documents) does not trigger any event + // content.RefreshContentFromDatabaseAsync triggers AfterRefresh _while_ refreshing + // etc... + // in addition some events do not make sense... we trigger Publish when moving + // a node, which we should not (the node is moved, not published...) etc. + } + /// /// Used ONLY for unit tests /// diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 8d8dfa0ef2..764e2e0aad 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Web; using System.Xml; using System.Xml.XPath; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using umbraco.BusinessLogic; using umbraco.BusinessLogic.Actions; @@ -15,7 +16,6 @@ using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.cache; using umbraco.cms.businesslogic.web; using umbraco.DataLayer; -using umbraco.IO; using umbraco.presentation.nodeFactory; using Action = umbraco.BusinessLogic.Actions.Action; using Node = umbraco.NodeFactory.Node; @@ -30,17 +30,17 @@ namespace umbraco #region Declarations // Sync access to disk file - private static readonly object _readerWriterSyncLock = new object(); + private static readonly object ReaderWriterSyncLock = new object(); // Sync access to internal cache - private static readonly object _xmlContentInternalSyncLock = new object(); + private static readonly object XmlContentInternalSyncLock = new object(); // Sync access to timestamps - private static readonly object _timestampSyncLock = new object(); + private static readonly object TimestampSyncLock = new object(); // Sync database access - private static readonly object _dbReadSyncLock = new object(); - private readonly string XmlContextContentItemKey = "UmbracoXmlContextContent"; + private static readonly object DbReadSyncLock = new object(); + private const string XmlContextContentItemKey = "UmbracoXmlContextContent"; private string _umbracoXmlDiskCacheFileName = string.Empty; private volatile XmlDocument _xmlContent; private DateTime _lastDiskCacheReadTime = DateTime.MinValue; @@ -65,46 +65,29 @@ namespace umbraco #endregion - #region Constructors - - static content() - { - //Trace.Write("Initializing content"); - //ThreadPool.QueueUserWorkItem( - // delegate - // { - // XmlDocument xmlDoc = Instance.XmlContentInternal; - // Trace.WriteLine("Content initialized"); - // }); - - Trace.WriteLine("Checking for xml content initialisation..."); - Instance.CheckXmlContentPopulation(); - } - - public content() - { - ; - } - - #endregion #region Singleton + private static readonly Lazy LazyInstance = new Lazy(() => new content()); + public static content Instance { - get { return Singleton.Instance; } + get + { + return LazyInstance.Value; + } } #endregion #region Properties - /// + /// /// Get content. First call to this property will initialize xmldoc /// subsequent calls will be blocked until initialization is done /// Further we cache(in context) xmlContent for each request to ensure that /// we always have the same XmlDoc throughout the whole request. - /// + /// public virtual XmlDocument XmlContent { get @@ -127,6 +110,9 @@ namespace umbraco get { return Instance.XmlContent; } } + //NOTE: We CANNOT use this for a double check lock because it is a property, not a field and to do double + // check locking in c# you MUST have a volatile field. Even thoug this wraps a volatile field it will still + // not work as expected for a double check lock because properties are treated differently in the clr. public virtual bool isInitializing { get { return _xmlContent == null; } @@ -135,6 +121,9 @@ namespace umbraco /// /// Internal reference to XmlContent /// + /// + /// Before returning we always check to ensure that the xml is loaded + /// protected virtual XmlDocument XmlContentInternal { get @@ -145,7 +134,7 @@ namespace umbraco } set { - lock (_xmlContentInternalSyncLock) + lock (XmlContentInternalSyncLock) { // Clear macro cache Cache.ClearCacheObjectTypes("umbraco.MacroCacheContent"); @@ -179,14 +168,14 @@ namespace umbraco if (UmbracoSettings.isXmlContentCacheDisabled) return; - lock (_timestampSyncLock) + lock (TimestampSyncLock) { if (_lastDiskCacheCheckTime > DateTime.UtcNow.AddSeconds(-1.0)) return; _lastDiskCacheCheckTime = DateTime.UtcNow; - lock (_xmlContentInternalSyncLock) + lock (XmlContentInternalSyncLock) { if (GetCacheFileUpdateTime() <= _lastDiskCacheReadTime) @@ -200,17 +189,17 @@ namespace umbraco /// /// Triggers the XML content population if necessary. /// - /// + /// Returns true of the XML was not populated, returns false if it was already populated private bool CheckXmlContentPopulation() { if (UmbracoSettings.XmlContentCheckForDiskChanges) CheckDiskCacheForUpdate(); - if (isInitializing) + if (_xmlContent == null) { - lock (_xmlContentInternalSyncLock) + lock (XmlContentInternalSyncLock) { - if (isInitializing) + if (_xmlContent == null) { LogHelper.Debug("Initializing content on thread '{0}' (Threadpool? {1})", true, @@ -504,7 +493,7 @@ namespace umbraco { // lock the xml cache so no other thread can write to it at the same time // note that some threads could read from it while we hold the lock, though - lock (_xmlContentInternalSyncLock) + lock (XmlContentInternalSyncLock) { // modify a clone of the cache because even though we're into the write-lock // we may have threads reading at the same time. why is this an option? @@ -551,7 +540,7 @@ namespace umbraco // making changes at the same time, they need to be queued int parentid = Documents[0].Id; - lock (_xmlContentInternalSyncLock) + lock (XmlContentInternalSyncLock) { // Make copy of memory content, we cannot make changes to the same document // the is read from elsewhere @@ -621,7 +610,7 @@ namespace umbraco // We need to lock content cache here, because we cannot allow other threads // making changes at the same time, they need to be queued - lock (_xmlContentInternalSyncLock) + lock (XmlContentInternalSyncLock) { // Make copy of memory content, we cannot make changes to the same document // the is read from elsewhere @@ -961,7 +950,7 @@ namespace umbraco /// private void DeleteXmlCache() { - lock (_readerWriterSyncLock) + lock (ReaderWriterSyncLock) { if (File.Exists(UmbracoXmlDiskCacheFileName)) { @@ -1040,7 +1029,7 @@ namespace umbraco /// private XmlDocument LoadContentFromDiskCache() { - lock (_readerWriterSyncLock) + lock (ReaderWriterSyncLock) { var xmlDoc = new XmlDocument(); LogHelper.Info("Loading content from disk cache..."); @@ -1106,7 +1095,7 @@ inner join cmsDocument on cmsDocument.nodeId = umbracoNode.id where cmsDocument.published = 1 order by umbracoNode.level, umbracoNode.sortOrder"; - lock (_dbReadSyncLock) + lock (DbReadSyncLock) { using ( IRecordsReader dr = SqlHelper.ExecuteReader(sql, @@ -1256,7 +1245,7 @@ order by umbracoNode.level, umbracoNode.sortOrder"; /// internal void PersistXmlToFile(XmlDocument xmlDoc) { - lock (_readerWriterSyncLock) + lock (ReaderWriterSyncLock) { if (xmlDoc != null) {