From 5b28260f96fef5eb44f456a52828ea1a55135ed7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 23 Jun 2016 09:39:31 +0200 Subject: [PATCH] U4-8469 - xml cache TLC --- .../PublishedContentCache.cs | 6 +- .../XmlPublishedCache/XmlPublishedContent.cs | 188 ++++++++---------- .../XmlPublishedCache/XmlPublishedProperty.cs | 43 ++-- 3 files changed, 121 insertions(+), 116 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index 8e9b529b7f..f2ef0e348f 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -277,15 +277,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private static IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing) { - return xmlNode == null - ? null - : (new XmlPublishedContent(xmlNode, isPreviewing)).CreateModel(); + return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing); } private static IEnumerable ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing) { return xmlNodes.Cast() - .Select(xmlNode => (new XmlPublishedContent(xmlNode, isPreviewing)).CreateModel()); + .Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing)); } #endregion diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 4bc2f2388a..5129542e46 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Xml; using System.Xml.Serialization; @@ -26,44 +25,26 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// /// The Xml node. /// A value indicating whether the published content is being previewed. - public XmlPublishedContent(XmlNode xmlNode, bool isPreviewing) + private XmlPublishedContent(XmlNode xmlNode, bool isPreviewing) { _xmlNode = xmlNode; _isPreviewing = isPreviewing; - InitializeStructure(); - Initialize(); - InitializeChildren(); - } - - /// - /// Initializes a new instance of the XmlPublishedContent class with an Xml node, - /// and a value indicating whether to lazy-initialize the instance. - /// - /// The Xml node. - /// A value indicating whether the published content is being previewed. - /// A value indicating whether to lazy-initialize the instance. - /// Lazy-initializationg is NOT thread-safe. - internal XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, bool lazyInitialize) - { - _xmlNode = xmlNode; - _isPreviewing = isPreviewing; - InitializeStructure(); - if (lazyInitialize == false) - { - Initialize(); - InitializeChildren(); - } } private readonly XmlNode _xmlNode; - - private bool _initialized; + private readonly bool _isPreviewing; + + private bool _nodeInitialized; + private bool _parentInitialized; private bool _childrenInitialized; - private readonly ICollection _children = new Collection(); + private IEnumerable _children = Enumerable.Empty(); private IPublishedContent _parent; - private int _id; + private PublishedContentType _contentType; + private Dictionary _properties; + + private int _id; private Guid _key; private int _template; private string _name; @@ -78,28 +59,24 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private DateTime _createDate; private DateTime _updateDate; private Guid _version; - private IPublishedProperty[] _properties; private int _sortOrder; private int _level; private bool _isDraft; - private readonly bool _isPreviewing; - private PublishedContentType _contentType; public override IEnumerable Children { get { - if (_initialized == false) - Initialize(); - if (_childrenInitialized == false) - InitializeChildren(); - return _children.OrderBy(x => x.SortOrder); + if (_nodeInitialized == false) InitializeNode(); + if (_childrenInitialized == false) InitializeChildren(); + return _children; } } public override IPublishedProperty GetProperty(string alias) { - return Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + IPublishedProperty property; + return _properties.TryGetValue(alias, out property) ? property : null; } // override to implement cache @@ -135,8 +112,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); + if (_parentInitialized == false) InitializeParent(); return _parent; } } @@ -145,8 +122,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _id; } } @@ -155,8 +131,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _key; } } @@ -165,8 +140,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _template; } } @@ -175,8 +149,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _sortOrder; } } @@ -185,8 +158,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _name; } } @@ -195,8 +167,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _docTypeAlias; } } @@ -205,8 +176,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _docTypeId; } } @@ -215,8 +185,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _writerName; } } @@ -225,8 +194,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _creatorName; } } @@ -235,8 +203,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _writerId; } } @@ -245,8 +212,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _creatorId; } } @@ -255,8 +221,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _path; } } @@ -265,8 +230,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _createDate; } } @@ -275,8 +239,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _updateDate; } } @@ -285,8 +248,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _version; } } @@ -295,8 +257,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _urlName; } } @@ -305,8 +266,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _level; } } @@ -315,8 +275,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _isDraft; } } @@ -325,9 +284,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); - return _properties; + if (_nodeInitialized == false) InitializeNode(); + return _properties.Values; } } @@ -335,24 +293,26 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache { get { - if (_initialized == false) - Initialize(); + if (_nodeInitialized == false) InitializeNode(); return _contentType; } } - private void InitializeStructure() + private void InitializeParent() { - // load parent if it exists and is a node + if (_xmlNode == null) return; - var parent = _xmlNode == null ? null : _xmlNode.ParentNode; + var parent = _xmlNode.ParentNode; if (parent == null) return; if (parent.Name == "node" || (parent.Attributes != null && parent.Attributes.GetNamedItem("isDoc") != null)) - _parent = (new XmlPublishedContent(parent, _isPreviewing, true)).CreateModel(); + _parent = Get(parent, _isPreviewing); + + // warn: this is not thread-safe... + _parentInitialized = true; } - private void Initialize() + private void InitializeNode() { if (_xmlNode == null) return; @@ -423,22 +383,27 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache if (nodes != null) foreach (XmlNode n in nodes) { + var attrs = n.Attributes; + if (attrs == null) continue; var alias = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema - ? n.Attributes.GetNamedItem("alias").Value + ? attrs.GetNamedItem("alias").Value : n.Name; propertyNodes[alias.ToLowerInvariant()] = n; } - _properties = _contentType.PropertyTypes.Select(p => - { - XmlNode n; - return propertyNodes.TryGetValue(p.PropertyTypeAlias.ToLowerInvariant(), out n) - ? new XmlPublishedProperty(p, _isPreviewing, n) - : new XmlPublishedProperty(p, _isPreviewing); - }).Cast().ToArray(); + _properties = _contentType.PropertyTypes.Select(p => + { + XmlNode n; + return propertyNodes.TryGetValue(p.PropertyTypeAlias.ToLowerInvariant(), out n) + ? new XmlPublishedProperty(p, _isPreviewing, n) + : new XmlPublishedProperty(p, _isPreviewing); + }).Cast().ToDictionary( + x => x.PropertyTypeAlias, + x => x, + StringComparer.OrdinalIgnoreCase); // warn: this is not thread-safe... - _initialized = true; + _nodeInitialized = true; } private void InitializeChildren() @@ -449,14 +414,37 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var childXPath = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema ? "node" : "* [@isDoc]"; var nav = _xmlNode.CreateNavigator(); var expr = nav.Compile(childXPath); - expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); + //expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); var iterator = nav.Select(expr); - while (iterator.MoveNext()) - _children.Add( - (new XmlPublishedContent(((IHasXmlNode)iterator.Current).GetNode(), _isPreviewing, true)).CreateModel()); - + + _children = iterator.Cast() + .Select(n => Get(((IHasXmlNode) n).GetNode(), _isPreviewing)) + .OrderBy(x => x.SortOrder) + .ToList(); + // warn: this is not thread-safe _childrenInitialized = true; } + + /// + /// Gets an IPublishedContent corresponding to an Xml cache node. + /// + /// The Xml node. + /// A value indicating whether we are previewing or not. + /// The IPublishedContent corresponding to the Xml cache node. + /// Maintains a per-request cache of IPublishedContent items in order to make + /// sure that we create only one instance of each for the duration of a request. The + /// returned IPublishedContent is a model, if models are enabled. + public static IPublishedContent Get(XmlNode node, bool isPreviewing) + { + // only 1 per request + + var attrs = node.Attributes; + var id = attrs == null ? null : attrs.GetNamedItem("id").Value; + if (id.IsNullOrWhiteSpace()) throw new InvalidOperationException("Node has no ID attribute."); + var cache = ApplicationContext.Current.ApplicationCache.RequestCache; + var key = "XMLPUBLISHEDCONTENT_" + id; // dont bother with preview, wont change during request in v7 + return (IPublishedContent) cache.GetCacheItem(key, () => (new XmlPublishedContent(node, isPreviewing)).CreateModel()); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs index d7caf7a83f..704a6a9bc9 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs @@ -2,9 +2,7 @@ using System; using System.Xml; using System.Xml.Serialization; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Models; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { @@ -17,10 +15,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache internal class XmlPublishedProperty : PublishedPropertyBase { private readonly string _xmlValue; // the raw, xml node value - private readonly Lazy _sourceValue; - private readonly Lazy _objectValue; - private readonly Lazy _xpathValue; - private readonly bool _isPreviewing; + + // in v7 we're not using XPath value so don't allocate that Lazy. + // as for the rest... we're single threaded here, keep it simple + //private readonly Lazy _sourceValue; + //private readonly Lazy _objectValue; + //private readonly Lazy _xpathValue; + private object _objectValue; + private bool _objectValueComputed; + private readonly bool _isPreviewing; /// /// Gets the raw value of the property. @@ -34,10 +37,26 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache get { return _xmlValue.Trim().Length > 0; } } - public override object Value { get { return _objectValue.Value; } } - public override object XPathValue { get { return _xpathValue.Value; } } + public override object Value + { + get + { + // NOT caching the source (intermediate) value since we'll never need it + // everything in Xml cache in v7 is per-request anyways + // also, properties should not be shared between requests and therefore + // are single threaded, so the following code should be safe & fast - public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing, XmlNode propertyXmlData) + if (_objectValueComputed) return _objectValue; + var sourceValue = PropertyType.ConvertDataToSource(_xmlValue, _isPreviewing); + _objectValue = PropertyType.ConvertSourceToObject(sourceValue, _isPreviewing); + _objectValueComputed = true; + return _objectValue; + } + } + + public override object XPathValue { get { throw new NotImplementedException(); } } + + public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing, XmlNode propertyXmlData) : this(propertyType, isPreviewing) { if (propertyXmlData == null) @@ -59,9 +78,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _xmlValue = string.Empty; _isPreviewing = isPreviewing; - _sourceValue = new Lazy(() => PropertyType.ConvertDataToSource(_xmlValue, _isPreviewing)); - _objectValue = new Lazy(() => PropertyType.ConvertSourceToObject(_sourceValue.Value, _isPreviewing)); - _xpathValue = new Lazy(() => PropertyType.ConvertSourceToXPath(_sourceValue.Value, _isPreviewing)); + //_sourceValue = new Lazy(() => PropertyType.ConvertDataToSource(_xmlValue, _isPreviewing)); + //_objectValue = new Lazy(() => PropertyType.ConvertSourceToObject(_sourceValue.Value, _isPreviewing)); + //_xpathValue = new Lazy(() => PropertyType.ConvertSourceToXPath(_sourceValue.Value, _isPreviewing)); } } } \ No newline at end of file