From e77471734660040ed168af3f9db06f651400908e Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 30 Jan 2018 13:14:01 +0100 Subject: [PATCH 1/5] U4-10897 - expose enlisted mutable Xml in events --- src/Umbraco.Core/Scoping/ScopeContext.cs | 14 ++++++++++++++ .../umbraco.presentation/SafeXmlReaderWriter.cs | 14 ++++++++++++-- src/Umbraco.Web/umbraco.presentation/content.cs | 6 +++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Scoping/ScopeContext.cs b/src/Umbraco.Core/Scoping/ScopeContext.cs index c79f02a0fa..eb382e5cc3 100644 --- a/src/Umbraco.Core/Scoping/ScopeContext.cs +++ b/src/Umbraco.Core/Scoping/ScopeContext.cs @@ -116,5 +116,19 @@ namespace Umbraco.Core.Scoping Enlisted[key] = enlistedOfT; return enlistedOfT.Item; } + + public T GetEnlisted(string key) + { + var enlistedObjects = _enlisted; + if (enlistedObjects == null) return default (T); + + IEnlistedObject enlisted; + if (enlistedObjects.TryGetValue(key, out enlisted) == false) + return default (T); + + var enlistedAs = enlisted as EnlistedObject; + if (enlistedAs == null) throw new InvalidOperationException("An item with the key exists, but with a different type."); + return enlistedAs.Item; + } } } diff --git a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs index 581a6a8983..936718ed23 100644 --- a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs @@ -19,6 +19,10 @@ namespace umbraco private bool _using; private bool _registerXmlChange; + // the default enlist priority is 100 + // enlist with a lower priority to ensure that anything "default" has a clean xml + private const int EnlistPriority = 90; + private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action refresh, Action apply, bool isWriter, bool scoped) { _releaser = releaser; @@ -30,6 +34,12 @@ namespace umbraco _xml = _isWriter ? Clone(xml) : xml; } + public static SafeXmlReaderWriter Get(IScopeProviderInternal scopeProvider) + { + var scopeContext = scopeProvider.Context; + return scopeContext == null ? null : scopeContext.GetEnlisted("safeXmlReaderWriter"); + } + public static SafeXmlReaderWriter Get(IScopeProviderInternal scopeProvider, AsyncLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) { var scopeContext = scopeProvider.Context; @@ -53,7 +63,7 @@ namespace umbraco (completed, item) => // action { item.DisposeForReal(completed); - }); + }, EnlistPriority); // ensure it's not already in-use - should never happen, just being super safe if (rw._using) @@ -141,4 +151,4 @@ namespace umbraco } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index a7d40fa088..c8daf43715 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -618,6 +618,10 @@ namespace umbraco { get { + // if there's a current enlisted reader/writer, use its xml + var safeXml = SafeXmlReaderWriter.Get(_scopeProvider); + if (safeXml != null) return safeXml.Xml; + var items = HttpContextItems; if (items == null) return XmlContentInternal; @@ -1259,4 +1263,4 @@ namespace umbraco #endregion } -} \ No newline at end of file +} From 9fd2f3434353f6540b5c9fd932e18552173708c4 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 30 Jan 2018 13:53:37 +0100 Subject: [PATCH 2/5] U4-10897 - defer Examine indexing until scope is all done --- src/Umbraco.Web/Search/ExamineEvents.cs | 222 +++++++++++++++++++----- 1 file changed, 180 insertions(+), 42 deletions(-) diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index e018bac214..9311ee75c5 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -11,6 +11,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Scoping; using Umbraco.Core.Sync; using Umbraco.Web.Cache; using UmbracoExamine; @@ -447,11 +448,11 @@ namespace Umbraco.Web.Search private static void ReIndexForMember(IMember member) { - ExamineManager.Instance.ReIndexNode( - member.ToXml(), IndexTypes.Member, - ExamineManager.Instance.IndexProviderCollection.OfType() - //ensure that only the providers are flagged to listen execute - .Where(x => x.EnableDefaultEventHandler)); + var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForMember(member)); + else + DeferedReIndexForMember.Execute(member); } /// @@ -479,19 +480,11 @@ namespace Umbraco.Web.Search private static void ReIndexForMedia(IMedia sender, bool isMediaPublished) { - var xml = sender.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", sender.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Media, - ExamineManager.Instance.IndexProviderCollection.OfType() - - //Index this item for all indexers if the media is not trashed, otherwise if the item is trashed - // then only index this for indexers supporting unpublished media - - .Where(x => isMediaPublished || (x.SupportUnpublishedContent)) - .Where(x => x.EnableDefaultEventHandler)); + var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForMedia(sender, isMediaPublished)); + else + DeferedReIndexForMedia.Execute(sender, isMediaPublished); } /// @@ -504,15 +497,11 @@ namespace Umbraco.Web.Search /// private static void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) { - ExamineManager.Instance.DeleteFromIndex( - entityId.ToString(CultureInfo.InvariantCulture), - ExamineManager.Instance.IndexProviderCollection.OfType() - - //if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, - // otherwise if keepIfUnpublished == false then remove from all indexes - - .Where(x => keepIfUnpublished == false || x.SupportUnpublishedContent == false) - .Where(x => x.EnableDefaultEventHandler)); + var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); + if (actions != null) + actions.Add(new DeferedDeleteIndex(entityId, keepIfUnpublished)); + else + DeferedDeleteIndex.Execute(entityId, keepIfUnpublished); } /// @@ -524,22 +513,171 @@ namespace Umbraco.Web.Search /// private static void ReIndexForContent(IContent sender, bool isContentPublished) { - var xml = sender.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", sender.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Content, - ExamineManager.Instance.IndexProviderCollection.OfType() - - //Index this item for all indexers if the content is published, otherwise if the item is not published - // then only index this for indexers supporting unpublished content - - .Where(x => isContentPublished || (x.SupportUnpublishedContent)) - .Where(x => x.EnableDefaultEventHandler)); + var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForContent(sender, isContentPublished)); + else + DeferedReIndexForContent.Execute(sender, isContentPublished); } - /// + private class DeferedActions + { + private readonly List _actions = new List(); + + public static DeferedActions Get(IScopeProvider scopeProvider) + { + var scopeContext = scopeProvider.Context; + if (scopeContext == null) return null; + + return scopeContext.Enlist("examineEvents", + () => new DeferedActions(), // creator + (completed, actions) => // action + { + if (completed) actions.Execute(); + }, 99); + } + + public void Add(DeferedAction action) + { + _actions.Add(action); + } + + private void Execute() + { + foreach (var action in _actions) + action.Execute(); + } + } + + private abstract class DeferedAction + { + public virtual void Execute() + { } + } + + private class DeferedReIndexForContent : DeferedAction + { + private readonly IContent _content; + private readonly bool _isPublished; + + public DeferedReIndexForContent(IContent content, bool isPublished) + { + _content = content; + _isPublished = isPublished; + } + + public override void Execute() + { + Execute(_content, _isPublished); + } + + public static void Execute(IContent content, bool isPublished) + { + var xml = content.ToXml(); + //add an icon attribute to get indexed + xml.Add(new XAttribute("icon", content.ContentType.Icon)); + + ExamineManager.Instance.ReIndexNode( + xml, IndexTypes.Content, + ExamineManager.Instance.IndexProviderCollection.OfType() + + //Index this item for all indexers if the content is published, otherwise if the item is not published + // then only index this for indexers supporting unpublished content + + .Where(x => isPublished || (x.SupportUnpublishedContent)) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedReIndexForMedia : DeferedAction + { + private readonly IMedia _media; + private readonly bool _isPublished; + + public DeferedReIndexForMedia(IMedia media, bool isPublished) + { + _media = media; + _isPublished = isPublished; + } + + public override void Execute() + { + Execute(_media, _isPublished); + } + + public static void Execute(IMedia media, bool isPublished) + { + var xml = media.ToXml(); + //add an icon attribute to get indexed + xml.Add(new XAttribute("icon", media.ContentType.Icon)); + + ExamineManager.Instance.ReIndexNode( + xml, IndexTypes.Media, + ExamineManager.Instance.IndexProviderCollection.OfType() + + //Index this item for all indexers if the media is not trashed, otherwise if the item is trashed + // then only index this for indexers supporting unpublished media + + .Where(x => isPublished || (x.SupportUnpublishedContent)) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedReIndexForMember : DeferedAction + { + private readonly IMember _member; + + public DeferedReIndexForMember(IMember member) + { + _member = member; + } + + public override void Execute() + { + Execute(_member); + } + + public static void Execute(IMember member) + { + ExamineManager.Instance.ReIndexNode( + member.ToXml(), IndexTypes.Member, + ExamineManager.Instance.IndexProviderCollection.OfType() + //ensure that only the providers are flagged to listen execute + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedDeleteIndex : DeferedAction + { + private readonly int _id; + private readonly bool _keepIfUnpublished; + + public DeferedDeleteIndex(int id, bool keepIfUnpublished) + { + _id = id; + _keepIfUnpublished = keepIfUnpublished; + } + + public override void Execute() + { + Execute(_id, _keepIfUnpublished); + } + + public static void Execute(int id, bool keepIfUnpublished) + { + ExamineManager.Instance.DeleteFromIndex( + id.ToString(CultureInfo.InvariantCulture), + ExamineManager.Instance.IndexProviderCollection.OfType() + + //if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, + // otherwise if keepIfUnpublished == false then remove from all indexes + + .Where(x => keepIfUnpublished == false || x.SupportUnpublishedContent == false) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + /// /// Converts a content node to XDocument /// /// @@ -583,4 +721,4 @@ namespace Umbraco.Web.Search return new XDocument(ExamineXmlExtensions.ToXElement(xNode)); } } -} \ No newline at end of file +} From 8f3a0b6919f01e0d5c142fd551a3c10b4e2540a1 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 30 Jan 2018 14:25:43 +0100 Subject: [PATCH 3/5] U4-10897 - change enlisting priorities --- src/Umbraco.Web/Cache/PageCacheRefresher.cs | 57 +++++++++++++++++++ src/Umbraco.Web/Search/ExamineEvents.cs | 9 ++- .../SafeXmlReaderWriter.cs | 2 +- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Cache/PageCacheRefresher.cs b/src/Umbraco.Web/Cache/PageCacheRefresher.cs index d9bb7e9b88..6e1c6f9803 100644 --- a/src/Umbraco.Web/Cache/PageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PageCacheRefresher.cs @@ -1,14 +1,71 @@ using System; +using System.Collections.Generic; +using Examine; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Sync; using umbraco; using umbraco.cms.businesslogic.web; +using Umbraco.Web.Models; using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace Umbraco.Web.Cache { + // debug + public class ExamineCultureEvents : IApplicationEventHandler + { + public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + //throw new NotImplementedException(); + } + + public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + //throw new NotImplementedException(); + } + + public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + var helper = new UmbracoHelper(UmbracoContext.Current); + ExamineManager.Instance.IndexProviderCollection["ExternalIndexer"].GatheringNodeData + += (sender, e) => ExamineCultureEvents.GatheringContentData(sender, e, helper); + PageCacheRefresher.CacheUpdated += (sender, args) => + { + var x = UmbracoContext.Current.ContentCache.GetById(1); + }; + } + + public static void GatheringContentData(object sender, IndexingNodeDataEventArgs e, UmbracoHelper helper) + { + // this should happen in the ExamineEvents class + var scopeProvider = ApplicationContext.Current.ScopeProvider; + using (var scope = scopeProvider.CreateScope()) // no scope, no context + { + var enlisted = scopeProvider.Context.Enlist("key", + () => new List(), // creator + (completed, list) => // action + { + // anything, really + }, + 1000); // default priority, used by SafeXmlReaderWriter, is 100 - run after it - fixme - internal should run < 100! + enlisted.Add(e.NodeId); + scope.Complete(); + } + + var c = ApplicationContext.Current.Services.ContentService.GetById(e.NodeId).GetCulture(); + IPublishedContent content = helper.TypedContent(e.NodeId); + if (content != null) + { + var culture = content.GetCulture(); + if (culture != null) + { + e.Fields.Add("culture", culture.ToString()); + } + } + } + } + /// /// PageCacheRefresher is the standard CacheRefresher used by Load-Balancing in Umbraco. /// diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index 9311ee75c5..cf4d6e33d4 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -25,7 +25,12 @@ namespace Umbraco.Web.Search /// public sealed class ExamineEvents : ApplicationEventHandler { - /// + // the default enlist priority is 100 + // enlist with a lower priority to ensure that anything "default" runs after us + // but greater that SafeXmlReaderWriter priority which is 60 + private const int EnlistPriority = 80; + + /// /// Once the application has started we should bind to all events and initialize the providers. /// /// @@ -534,7 +539,7 @@ namespace Umbraco.Web.Search (completed, actions) => // action { if (completed) actions.Execute(); - }, 99); + }, EnlistPriority); } public void Add(DeferedAction action) diff --git a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs index 936718ed23..b42ae55706 100644 --- a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs @@ -21,7 +21,7 @@ namespace umbraco // the default enlist priority is 100 // enlist with a lower priority to ensure that anything "default" has a clean xml - private const int EnlistPriority = 90; + private const int EnlistPriority = 60; private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action refresh, Action apply, bool isWriter, bool scoped) { From 80257b668438f9afcb004236353c434f50a49ce1 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 30 Jan 2018 14:26:59 +0100 Subject: [PATCH 4/5] Revert "U4-10897 - change enlisting priorities" This reverts commit 8f3a0b6919f01e0d5c142fd551a3c10b4e2540a1. --- src/Umbraco.Web/Cache/PageCacheRefresher.cs | 57 ------------------- src/Umbraco.Web/Search/ExamineEvents.cs | 9 +-- .../SafeXmlReaderWriter.cs | 2 +- 3 files changed, 3 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Web/Cache/PageCacheRefresher.cs b/src/Umbraco.Web/Cache/PageCacheRefresher.cs index 6e1c6f9803..d9bb7e9b88 100644 --- a/src/Umbraco.Web/Cache/PageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PageCacheRefresher.cs @@ -1,71 +1,14 @@ using System; -using System.Collections.Generic; -using Examine; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Sync; using umbraco; using umbraco.cms.businesslogic.web; -using Umbraco.Web.Models; using Umbraco.Web.PublishedCache.XmlPublishedCache; namespace Umbraco.Web.Cache { - // debug - public class ExamineCultureEvents : IApplicationEventHandler - { - public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - //throw new NotImplementedException(); - } - - public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - //throw new NotImplementedException(); - } - - public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) - { - var helper = new UmbracoHelper(UmbracoContext.Current); - ExamineManager.Instance.IndexProviderCollection["ExternalIndexer"].GatheringNodeData - += (sender, e) => ExamineCultureEvents.GatheringContentData(sender, e, helper); - PageCacheRefresher.CacheUpdated += (sender, args) => - { - var x = UmbracoContext.Current.ContentCache.GetById(1); - }; - } - - public static void GatheringContentData(object sender, IndexingNodeDataEventArgs e, UmbracoHelper helper) - { - // this should happen in the ExamineEvents class - var scopeProvider = ApplicationContext.Current.ScopeProvider; - using (var scope = scopeProvider.CreateScope()) // no scope, no context - { - var enlisted = scopeProvider.Context.Enlist("key", - () => new List(), // creator - (completed, list) => // action - { - // anything, really - }, - 1000); // default priority, used by SafeXmlReaderWriter, is 100 - run after it - fixme - internal should run < 100! - enlisted.Add(e.NodeId); - scope.Complete(); - } - - var c = ApplicationContext.Current.Services.ContentService.GetById(e.NodeId).GetCulture(); - IPublishedContent content = helper.TypedContent(e.NodeId); - if (content != null) - { - var culture = content.GetCulture(); - if (culture != null) - { - e.Fields.Add("culture", culture.ToString()); - } - } - } - } - /// /// PageCacheRefresher is the standard CacheRefresher used by Load-Balancing in Umbraco. /// diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index cf4d6e33d4..9311ee75c5 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -25,12 +25,7 @@ namespace Umbraco.Web.Search /// public sealed class ExamineEvents : ApplicationEventHandler { - // the default enlist priority is 100 - // enlist with a lower priority to ensure that anything "default" runs after us - // but greater that SafeXmlReaderWriter priority which is 60 - private const int EnlistPriority = 80; - - /// + /// /// Once the application has started we should bind to all events and initialize the providers. /// /// @@ -539,7 +534,7 @@ namespace Umbraco.Web.Search (completed, actions) => // action { if (completed) actions.Execute(); - }, EnlistPriority); + }, 99); } public void Add(DeferedAction action) diff --git a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs index b42ae55706..936718ed23 100644 --- a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs @@ -21,7 +21,7 @@ namespace umbraco // the default enlist priority is 100 // enlist with a lower priority to ensure that anything "default" has a clean xml - private const int EnlistPriority = 60; + private const int EnlistPriority = 90; private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action refresh, Action apply, bool isWriter, bool scoped) { From 4870afd2d3f420ab0c769eaa7b1e2d675db66ef6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 30 Jan 2018 14:27:58 +0100 Subject: [PATCH 5/5] U4-10897 - change enlisting priorities --- src/Umbraco.Web/Search/ExamineEvents.cs | 9 +++++++-- .../umbraco.presentation/SafeXmlReaderWriter.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index 9311ee75c5..cf4d6e33d4 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -25,7 +25,12 @@ namespace Umbraco.Web.Search /// public sealed class ExamineEvents : ApplicationEventHandler { - /// + // the default enlist priority is 100 + // enlist with a lower priority to ensure that anything "default" runs after us + // but greater that SafeXmlReaderWriter priority which is 60 + private const int EnlistPriority = 80; + + /// /// Once the application has started we should bind to all events and initialize the providers. /// /// @@ -534,7 +539,7 @@ namespace Umbraco.Web.Search (completed, actions) => // action { if (completed) actions.Execute(); - }, 99); + }, EnlistPriority); } public void Add(DeferedAction action) diff --git a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs index 936718ed23..b42ae55706 100644 --- a/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Web/umbraco.presentation/SafeXmlReaderWriter.cs @@ -21,7 +21,7 @@ namespace umbraco // the default enlist priority is 100 // enlist with a lower priority to ensure that anything "default" has a clean xml - private const int EnlistPriority = 90; + private const int EnlistPriority = 60; private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action refresh, Action apply, bool isWriter, bool scoped) {