diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 2892a04f9022..d2454b4e1f72 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Xml.Serialization; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Collections; @@ -19,7 +20,6 @@ internal class Property : PublishedPropertyBase private readonly bool _isMember; private readonly bool _isPreviewing; - private readonly object _locko = new(); private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; // the invariant-neutral source and inter values @@ -33,7 +33,7 @@ internal class Property : PublishedPropertyBase private object? _interValue; // the variant source and inter values - private Dictionary? _sourceValues; + private ConcurrentDictionary? _sourceValues; private string? _valuesCacheKey; @@ -68,7 +68,7 @@ public Property( { if (_sourceValues == null) { - _sourceValues = new Dictionary(); + _sourceValues = InitializeConcurrentDictionary(); } _sourceValues[new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)] @@ -125,30 +125,27 @@ public override bool HasValue(string? culture = null, string? segment = null) return hasValue.Value; } - lock (_locko) + value = GetInterValue(culture, segment); + hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); + if (hasValue.HasValue) { - value = GetInterValue(culture, segment); - hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); - if (hasValue.HasValue) - { - return hasValue.Value; - } - - CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); + return hasValue.Value; + } - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); - if (!cacheValues.ObjectInitialized) - { - cacheValues.ObjectValue = - PropertyType.ConvertInterToObject(_content, initialCacheLevel, value, _isPreviewing); - cacheValues.ObjectInitialized = true; - } + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - value = cacheValues.ObjectValue; - return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; + if (!cacheValues.ObjectInitialized) + { + cacheValues.ObjectValue = + PropertyType.ConvertInterToObject(_content, initialCacheLevel, value, _isPreviewing); + cacheValues.ObjectInitialized = true; } + + value = cacheValues.ObjectValue; + return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; } public override object? GetSourceValue(string? culture = null, string? segment = null) @@ -160,19 +157,16 @@ public override bool HasValue(string? culture = null, string? segment = null) return _sourceValue; } - lock (_locko) + if (_sourceValues == null) { - if (_sourceValues == null) - { - return null; - } - - return _sourceValues.TryGetValue( - new CompositeStringStringKey(culture, segment), - out SourceInterValue? sourceValue) - ? sourceValue.SourceValue - : null; + return null; } + + return _sourceValues.TryGetValue( + new CompositeStringStringKey(culture, segment), + out SourceInterValue? sourceValue) + ? sourceValue.SourceValue + : null; } private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel) @@ -227,7 +221,6 @@ private CacheValues GetCacheValues(IAppCache? cache) return (CacheValues)cache.Get(ValuesCacheKey, () => new CacheValues())!; } - // this is always invoked from within a lock, so does not require its own lock private object? GetInterValue(string? culture, string? segment) { if (culture == string.Empty && segment == string.Empty) @@ -244,7 +237,7 @@ private CacheValues GetCacheValues(IAppCache? cache) if (_sourceValues == null) { - _sourceValues = new Dictionary(); + _sourceValues = InitializeConcurrentDictionary(); } var k = new CompositeStringStringKey(culture, segment); @@ -273,23 +266,20 @@ private CacheValues GetCacheValues(IAppCache? cache) _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object? value; - lock (_locko) - { - CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); + CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - if (cacheValues.ObjectInitialized) - { - return cacheValues.ObjectValue; - } - - cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); - cacheValues.ObjectInitialized = true; - value = cacheValues.ObjectValue; + if (cacheValues.ObjectInitialized) + { + return cacheValues.ObjectValue; } + cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); + cacheValues.ObjectInitialized = true; + value = cacheValues.ObjectValue; + return value; } @@ -298,22 +288,19 @@ private CacheValues GetCacheValues(IAppCache? cache) { _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); - lock (_locko) - { - CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); - - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); - if (cacheValues.XPathInitialized) - { - return cacheValues.XPathValue; - } + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); - cacheValues.XPathInitialized = true; + if (cacheValues.XPathInitialized) + { return cacheValues.XPathValue; } + + cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); + cacheValues.XPathInitialized = true; + return cacheValues.XPathValue; } public override object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null) @@ -321,18 +308,16 @@ private CacheValues GetCacheValues(IAppCache? cache) _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object? value; - lock (_locko) - { - CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment); + CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment); - // initial reference cache level always is .Content - const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; - object? GetDeliveryApiObject() => PropertyType.ConvertInterToDeliveryApiObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing, expanding); - value = expanding - ? GetDeliveryApiExpandedObject(cacheValues, GetDeliveryApiObject) - : GetDeliveryApiDefaultObject(cacheValues, GetDeliveryApiObject); - } + // initial reference cache level always is .Content + const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; + + object? GetDeliveryApiObject() => PropertyType.ConvertInterToDeliveryApiObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing, expanding); + value = expanding + ? GetDeliveryApiExpandedObject(cacheValues, GetDeliveryApiObject) + : GetDeliveryApiDefaultObject(cacheValues, GetDeliveryApiObject); return value; } @@ -382,9 +367,8 @@ private class CacheValue private class CacheValues : CacheValue { - private Dictionary? _values; + private ConcurrentDictionary? _values; - // this is always invoked from within a lock, so does not require its own lock public CacheValue For(string? culture, string? segment) { if (culture == string.Empty && segment == string.Empty) @@ -394,7 +378,7 @@ public CacheValue For(string? culture, string? segment) if (_values == null) { - _values = new Dictionary(); + _values = InitializeConcurrentDictionary(); } var k = new CompositeStringStringKey(culture, segment); @@ -431,5 +415,9 @@ public string? Segment public object? InterValue { get; set; } } + private static ConcurrentDictionary InitializeConcurrentDictionary() + where TKey : notnull + => new(-1, 5); + #endregion }