Merge remote-tracking branch 'origin/v8/8.0' into temp8-4011

This commit is contained in:
Shannon
2019-04-01 14:16:13 +11:00
17 changed files with 533 additions and 299 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="4.6.0">
<metadata minClientVersion="4.1.0">
<id>UmbracoCms.Core</id>
<version>8.0.0</version>
<title>Umbraco Cms Core Binaries</title>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="4.6.0">
<metadata minClientVersion="4.1.0">
<id>UmbracoCms.Web</id>
<version>8.0.0</version>
<title>Umbraco Cms Core Binaries</title>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="4.6.0">
<metadata minClientVersion="4.1.0">
<id>UmbracoCms</id>
<version>8.0.0</version>
<title>Umbraco Cms</title>

View File

@@ -2,39 +2,61 @@
namespace Umbraco.Core.Scoping
{
// base class for an object that will be enlisted in scope context, if any. it
// must be used in a 'using' block, and if not scoped, released when disposed,
// else when scope context runs enlisted actions
/// <summary>
/// Provides a base class for scope contextual objects.
/// </summary>
/// <remarks>
/// <para>A scope contextual object is enlisted in the current scope context,
/// if any, and released when the context exists. It must be used in a 'using'
/// block, and will be released when disposed, if not part of a scope.</para>
/// </remarks>
public abstract class ScopeContextualBase : IDisposable
{
private bool _using, _scoped;
private bool _scoped;
/// <summary>
/// Gets a contextual object.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="scopeProvider">A scope provider.</param>
/// <param name="key">A context key for the object.</param>
/// <param name="ctor">A function producing the contextual object.</param>
/// <returns>The contextual object.</returns>
/// <remarks>
/// <para></para>
/// </remarks>
public static T Get<T>(IScopeProvider scopeProvider, string key, Func<bool, T> ctor)
where T : ScopeContextualBase
{
// no scope context = create a non-scoped object
var scopeContext = scopeProvider.Context;
if (scopeContext == null)
return ctor(false);
// create & enlist the scoped object
var w = scopeContext.Enlist("ScopeContextualBase_" + key,
() => ctor(true),
(completed, item) => { item.Release(completed); });
if (w._using) throw new InvalidOperationException("panic: used.");
w._using = true;
w._scoped = true;
return w;
}
/// <inheritdoc />
/// <remarks>
/// <para>If not scoped, then this releases the contextual object.</para>
/// </remarks>
public void Dispose()
{
_using = false;
if (_scoped == false)
Release(true);
}
/// <summary>
/// Releases the contextual object.
/// </summary>
/// <param name="completed">A value indicating whether the scoped operation completed.</param>
public abstract void Release(bool completed);
}
}

View File

@@ -2875,7 +2875,14 @@ namespace Umbraco.Core.Services.Implement
{
foreach (var property in blueprint.Properties)
{
content.SetValue(property.Alias, property.GetValue(culture), culture);
if (property.PropertyType.VariesByCulture())
{
content.SetValue(property.Alias, property.GetValue(culture), culture);
}
else
{
content.SetValue(property.Alias, property.GetValue());
}
}
content.Name = blueprint.Name;

View File

@@ -5,6 +5,7 @@ using Moq;
using NUnit.Framework;
using Umbraco.Core.Scoping;
using Umbraco.Web.PublishedCache.NuCache;
using Umbraco.Web.PublishedCache.NuCache.Snap;
namespace Umbraco.Tests.Cache
{
@@ -388,8 +389,7 @@ namespace Umbraco.Tests.Cache
// collect liveGen
GC.Collect();
SnapDictionary<int, string>.GenerationObject genObj;
Assert.IsTrue(d.Test.GenerationObjects.TryPeek(out genObj));
Assert.IsTrue(d.Test.GenObjs.TryPeek(out var genObj));
genObj = null;
// in Release mode, it works, but in Debug mode, the weak reference is still alive
@@ -399,14 +399,14 @@ namespace Umbraco.Tests.Cache
GC.Collect();
#endif
Assert.IsTrue(d.Test.GenerationObjects.TryPeek(out genObj));
Assert.IsFalse(genObj.WeakReference.IsAlive); // snapshot is gone, along with its reference
Assert.IsTrue(d.Test.GenObjs.TryPeek(out genObj));
Assert.IsFalse(genObj.WeakGenRef.IsAlive); // snapshot is gone, along with its reference
await d.CollectAsync();
Assert.AreEqual(0, d.Test.GetValues(1).Length); // null value is gone
Assert.AreEqual(0, d.Count); // item is gone
Assert.AreEqual(0, d.Test.GenerationObjects.Count);
Assert.AreEqual(0, d.Test.GenObjs.Count);
Assert.AreEqual(0, d.SnapCount); // snapshot is gone
Assert.AreEqual(0, d.GenCount); // and generation has been dequeued
}
@@ -632,7 +632,7 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(1, d.Test.LiveGen);
Assert.IsTrue(d.Test.NextGen);
using (d.GetWriter(GetScopeProvider()))
using (d.GetScopedWriteLock(GetScopeProvider()))
{
var s1 = d.CreateSnapshot();
@@ -685,7 +685,7 @@ namespace Umbraco.Tests.Cache
Assert.IsFalse(d.Test.NextGen);
Assert.AreEqual("uno", s2.Get(1));
using (d.GetWriter(GetScopeProvider()))
using (d.GetScopedWriteLock(GetScopeProvider()))
{
// gen 3
Assert.AreEqual(2, d.Test.GetValues(1).Length);
@@ -712,16 +712,102 @@ namespace Umbraco.Tests.Cache
}
[Test]
public void NestedWriteLocking()
public void NestedWriteLocking1()
{
var d = new SnapDictionary<int, string>();
var t = d.Test;
t.CollectAuto = false;
Assert.AreEqual(0, d.CreateSnapshot().Gen);
// no scope context: writers nest, last one to be disposed commits
var scopeProvider = GetScopeProvider();
using (var w1 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(2, t.WLocked);
Assert.IsTrue(t.NextGen);
Assert.AreNotSame(w1, w2); // get a new writer each time
d.Set(1, "one");
Assert.AreEqual(0, d.CreateSnapshot().Gen);
}
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(0, d.CreateSnapshot().Gen);
}
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(0, t.WLocked);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, d.CreateSnapshot().Gen);
}
[Test]
public void NestedWriteLocking2()
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
var scopeProvider = GetScopeProvider();
using (d.GetWriter(scopeProvider))
Assert.AreEqual(0, d.CreateSnapshot().Gen);
// scope context: writers enlist
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
using (var w1 = d.GetScopedWriteLock(scopeProvider))
{
using (d.GetWriter(scopeProvider))
using (var w2 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreSame(w1, w2);
d.Set(1, "one");
}
}
}
[Test]
public void NestedWriteLocking3()
{
var d = new SnapDictionary<int, string>();
var t = d.Test;
t.CollectAuto = false;
Assert.AreEqual(0, d.CreateSnapshot().Gen);
var scopeContext = new ScopeContext();
var scopeProvider1 = GetScopeProvider();
var scopeProvider2 = GetScopeProvider(scopeContext);
using (var w1 = d.GetScopedWriteLock(scopeProvider1))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider2))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(2, t.WLocked);
Assert.IsTrue(t.NextGen);
Assert.AreNotSame(w1, w2);
d.Set(1, "one");
}
}
@@ -764,7 +850,7 @@ namespace Umbraco.Tests.Cache
var scopeProvider = GetScopeProvider();
using (d.GetWriter(scopeProvider))
using (d.GetScopedWriteLock(scopeProvider))
{
// gen 3
Assert.AreEqual(2, d.Test.GetValues(1).Length);
@@ -809,7 +895,7 @@ namespace Umbraco.Tests.Cache
var scopeProvider = GetScopeProvider();
using (d.GetWriter(scopeProvider))
using (d.GetScopedWriteLock(scopeProvider))
{
// creating a snapshot in a write-lock does NOT return the "current" content
// it uses the previous snapshot, so new snapshot created only on release
@@ -846,9 +932,10 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s2.Gen);
Assert.AreEqual("uno", s2.Get(1));
var scopeProvider = GetScopeProvider(true);
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
using (d.GetWriter(scopeProvider))
using (d.GetScopedWriteLock(scopeProvider))
{
// creating a snapshot in a write-lock does NOT return the "current" content
// it uses the previous snapshot, so new snapshot created only on release
@@ -867,7 +954,7 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s4.Gen);
Assert.AreEqual("uno", s4.Get(1));
((ScopeContext) scopeProvider.Context).ScopeExit(true);
scopeContext.ScopeExit(true);
var s5 = d.CreateSnapshot();
Assert.AreEqual(3, s5.Gen);
@@ -878,7 +965,8 @@ namespace Umbraco.Tests.Cache
public void ScopeLocking2()
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
var t = d.Test;
t.CollectAuto = false;
// gen 1
d.Set(1, "one");
@@ -891,12 +979,13 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s2.Gen);
Assert.AreEqual("uno", s2.Get(1));
var scopeProviderMock = new Mock<IScopeProvider>();
var scopeContext = new ScopeContext();
scopeProviderMock.Setup(x => x.Context).Returns(scopeContext);
var scopeProvider = scopeProviderMock.Object;
Assert.AreEqual(2, t.LiveGen);
Assert.IsFalse(t.NextGen);
using (d.GetWriter(scopeProvider))
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
using (d.GetScopedWriteLock(scopeProvider))
{
// creating a snapshot in a write-lock does NOT return the "current" content
// it uses the previous snapshot, so new snapshot created only on release
@@ -905,18 +994,35 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s3.Gen);
Assert.AreEqual("uno", s3.Get(1));
// we made some changes, so a next gen is required
Assert.AreEqual(3, t.LiveGen);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, t.WLocked);
// but live snapshot contains changes
var ls = d.Test.LiveSnapshot;
var ls = t.LiveSnapshot;
Assert.AreEqual("ein", ls.Get(1));
Assert.AreEqual(3, ls.Gen);
}
// nothing is committed until scope exits
Assert.AreEqual(3, t.LiveGen);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, t.WLocked);
// no changes until exit
var s4 = d.CreateSnapshot();
Assert.AreEqual(2, s4.Gen);
Assert.AreEqual("uno", s4.Get(1));
scopeContext.ScopeExit(false);
// now things have changed
Assert.AreEqual(2, t.LiveGen);
Assert.IsFalse(t.NextGen);
Assert.AreEqual(0, t.WLocked);
// no changes since not completed
var s5 = d.CreateSnapshot();
Assert.AreEqual(2, s5.Gen);
Assert.AreEqual("uno", s5.Get(1));
@@ -955,12 +1061,92 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual("four", all[3]);
}
private IScopeProvider GetScopeProvider(bool withContext = false)
[Test]
public void DontPanic()
{
var scopeProviderMock = new Mock<IScopeProvider>();
var scopeContext = withContext ? new ScopeContext() : null;
scopeProviderMock.Setup(x => x.Context).Returns(scopeContext);
var scopeProvider = scopeProviderMock.Object;
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
Assert.IsNull(d.Test.GenObj);
// gen 1
d.Set(1, "one");
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(1, d.Test.LiveGen);
Assert.IsNull(d.Test.GenObj);
var s1 = d.CreateSnapshot();
Assert.IsFalse(d.Test.NextGen);
Assert.AreEqual(1, d.Test.LiveGen);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(1, d.Test.GenObj.Gen);
Assert.AreEqual(1, s1.Gen);
Assert.AreEqual("one", s1.Get(1));
d.Set(1, "uno");
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(2, d.Test.LiveGen);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(1, d.Test.GenObj.Gen);
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
// scopeProvider.Context == scopeContext -> writer is scoped
// writer is scope contextual and scoped
// when disposed, nothing happens
// when the context exists, the writer is released
using (d.GetScopedWriteLock(scopeProvider))
{
d.Set(1, "ein");
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
}
// writer has not released
Assert.AreEqual(1, d.Test.WLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
// nothing changed
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(3, d.Test.LiveGen);
// panic!
var s2 = d.CreateSnapshot();
Assert.AreEqual(1, d.Test.WLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsTrue(d.Test.NextGen);
// release writer
scopeContext.ScopeExit(true);
Assert.AreEqual(0, d.Test.WLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsTrue(d.Test.NextGen);
var s3 = d.CreateSnapshot();
Assert.AreEqual(0, d.Test.WLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(3, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsFalse(d.Test.NextGen);
}
private IScopeProvider GetScopeProvider(ScopeContext scopeContext = null)
{
var scopeProvider = Mock.Of<IScopeProvider>();
Mock.Get(scopeProvider)
.Setup(x => x.Context).Returns(scopeContext);
return scopeProvider;
}
}

View File

@@ -62,8 +62,8 @@ function valPropertyMsg(serverValidationManager) {
if (!watcher) {
watcher = scope.$watch("currentProperty.value",
function (newValue, oldValue) {
if (!newValue || angular.equals(newValue, oldValue)) {
if (angular.equals(newValue, oldValue)) {
return;
}
@@ -78,10 +78,12 @@ function valPropertyMsg(serverValidationManager) {
// based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
// is the only one, then we'll clear.
if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
if (errCount === 0 || (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
scope.errorMsg = "";
formCtrl.$setValidity('valPropertyMsg', true);
stopWatch();
} else if (showValidation && scope.errorMsg === "") {
formCtrl.$setValidity('valPropertyMsg', false);
scope.errorMsg = getErrorMsg();
}
}, true);
}
@@ -152,6 +154,7 @@ function valPropertyMsg(serverValidationManager) {
showValidation = true;
if (hasError && scope.errorMsg === "") {
scope.errorMsg = getErrorMsg();
startWatch();
}
else if (!hasError) {
scope.errorMsg = "";

View File

@@ -113,7 +113,12 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
$scope.exitPreview = function () {
var culture = $location.search().culture || getParameterByName("culture");
var relativeUrl = "/" + $scope.pageId +'?culture='+ culture;
var relativeUrl = "/" + $scope.pageId;
if(culture){
relativeUrl +='?culture='+ culture;
}
window.top.location.href = "../preview/end?redir=" + encodeURIComponent(relativeUrl);
};

View File

@@ -46,8 +46,7 @@
if (data.language !== "undefined") {
var lang = vm.languages.filter(function (l) {
return matchLanguageById(l, data.language.Id);
return matchLanguageById(l, data.language);
});
if (lang.length > 0) {
vm.language = lang[0];

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
@@ -14,11 +15,18 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
[DefaultPropertyValueConverter]
public class MediaPickerValueConverter : PropertyValueConverterBase
{
// hard-coding "image" here but that's how it works at UI level too
private const string ImageTypeAlias = "image";
private readonly IPublishedModelFactory _publishedModelFactory;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
public MediaPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor)
public MediaPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor,
IPublishedModelFactory publishedModelFactory)
{
_publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor));
_publishedSnapshotAccessor = publishedSnapshotAccessor ??
throw new ArgumentNullException(nameof(publishedSnapshotAccessor));
_publishedModelFactory = publishedModelFactory;
}
public override bool IsConverter(PublishedPropertyType propertyType)
@@ -31,15 +39,19 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
var isMultiple = IsMultipleDataType(propertyType.DataType);
var isOnlyImages = IsOnlyImagesDataType(propertyType.DataType);
// hard-coding "image" here but that's how it works at UI level too
return isMultiple
? (isOnlyImages ? typeof(IEnumerable<>).MakeGenericType(ModelType.For("image")) : typeof(IEnumerable<IPublishedContent>))
: (isOnlyImages ? ModelType.For("image") : typeof(IPublishedContent));
? isOnlyImages
? typeof(IEnumerable<>).MakeGenericType(ModelType.For(ImageTypeAlias))
: typeof(IEnumerable<IPublishedContent>)
: isOnlyImages
? ModelType.For(ImageTypeAlias)
: typeof(IPublishedContent);
}
public override PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType)
=> PropertyCacheLevel.Snapshot;
{
return PropertyCacheLevel.Snapshot;
}
private bool IsMultipleDataType(PublishedDataType dataType)
{
@@ -53,26 +65,31 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
return config.OnlyImages;
}
public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType, object source, bool preview)
public override object ConvertSourceToIntermediate(IPublishedElement owner, PublishedPropertyType propertyType,
object source, bool preview)
{
if (source == null) return null;
var nodeIds = source.ToString()
.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
.Select(Udi.Parse)
.ToArray();
return nodeIds;
}
public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object source, bool preview)
public override object ConvertIntermediateToObject(IPublishedElement owner, PublishedPropertyType propertyType,
PropertyCacheLevel cacheLevel, object source, bool preview)
{
if (source == null)
{
return null;
}
var isMultiple = IsMultipleDataType(propertyType.DataType);
var isOnlyImages = IsOnlyImagesDataType(propertyType.DataType);
var udis = (Udi[]) source;
var mediaItems = isOnlyImages
? _publishedModelFactory.CreateModelList(ImageTypeAlias)
: new List<IPublishedContent>();
if (source == null) return isMultiple ? mediaItems : null;
var udis = (Udi[])source;
var mediaItems = new List<IPublishedContent>();
if (udis.Any())
{
foreach (var udi in udis)
@@ -84,12 +101,15 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
mediaItems.Add(item);
}
if (IsMultipleDataType(propertyType.DataType))
return mediaItems;
return mediaItems.FirstOrDefault();
return isMultiple ? mediaItems : FirstOrDefault(mediaItems);
}
return source;
}
private object FirstOrDefault(IList mediaItems)
{
return mediaItems.Count == 0 ? null : mediaItems[0];
}
}
}

View File

@@ -8,6 +8,7 @@ using CSharpTest.Net.Collections;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Scoping;
using Umbraco.Web.PublishedCache.NuCache.Snap;
namespace Umbraco.Web.PublishedCache.NuCache
{
@@ -29,8 +30,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
private readonly ILogger _logger;
private BPlusTree<int, ContentNodeKit> _localDb;
private readonly ConcurrentQueue<GenRefRef> _genRefRefs;
private GenRefRef _genRefRef;
private readonly ConcurrentQueue<GenObj> _genObjs;
private GenObj _genObj;
private readonly object _wlocko = new object();
private readonly object _rlocko = new object();
private long _liveGen, _floorGen;
@@ -64,8 +65,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
_contentTypesByAlias = new ConcurrentDictionary<string, LinkedNode<PublishedContentType>>(StringComparer.InvariantCultureIgnoreCase);
_xmap = new ConcurrentDictionary<Guid, int>();
_genRefRefs = new ConcurrentQueue<GenRefRef>();
_genRefRef = null; // no initial gen exists
_genObjs = new ConcurrentQueue<GenObj>();
_genObj = null; // no initial gen exists
_liveGen = _floorGen = 0;
_nextGen = false; // first time, must create a snapshot
_collectAuto = true; // collect automatically by default
@@ -91,12 +92,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
// a scope contextual that represents a locked writer to the dictionary
private class ContentStoreWriter : ScopeContextualBase
private class ScopedWriteLock : ScopeContextualBase
{
private readonly WriteLockInfo _lockinfo = new WriteLockInfo();
private ContentStore _store;
private readonly ContentStore _store;
private int _released;
public ContentStoreWriter(ContentStore store, bool scoped)
public ScopedWriteLock(ContentStore store, bool scoped)
{
_store = store;
store.Lock(_lockinfo, scoped);
@@ -104,17 +106,17 @@ namespace Umbraco.Web.PublishedCache.NuCache
public override void Release(bool completed)
{
if (_store== null) return;
if (Interlocked.CompareExchange(ref _released, 1, 0) != 0)
return;
_store.Release(_lockinfo, completed);
_store = null;
}
}
// gets a scope contextual representing a locked writer to the dictionary
// TODO: GetScopedWriter? should the dict have a ref onto the scope provider?
public IDisposable GetWriter(IScopeProvider scopeProvider)
public IDisposable GetScopedWriteLock(IScopeProvider scopeProvider)
{
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ContentStoreWriter(this, scoped));
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped));
}
private void Lock(WriteLockInfo lockInfo, bool forceGen = false)
@@ -131,12 +133,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
_wlocked++;
lockInfo.Count = true;
if (_nextGen == false || (forceGen && _wlocked == 1)) // if true already... ok to have "holes" in generation objects
if (_nextGen == false || (forceGen && _wlocked == 1))
{
// because we are changing things, a new generation
// is created, which will trigger a new snapshot
_nextGen = true;
if (_nextGen)
_genObjs.Enqueue(_genObj = new GenObj(_liveGen));
_liveGen += 1;
_nextGen = true;
}
}
}
@@ -212,7 +216,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
else
dictionary.TryUpdate(key, link.Next, link);
}
}
#endregion
@@ -836,8 +839,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
// if no next generation is required, and we already have one,
// use it and create a new snapshot
if (_nextGen == false && _genRefRef != null)
return new Snapshot(this, _genRefRef.GetGenRef()
if (_nextGen == false && _genObj != null)
return new Snapshot(this, _genObj.GetGenRef()
#if DEBUG
, _logger
#endif
@@ -852,15 +855,15 @@ namespace Umbraco.Web.PublishedCache.NuCache
var snapGen = _nextGen ? _liveGen - 1 : _liveGen;
// create a new gen ref unless we already have it
if (_genRefRef == null)
_genRefRefs.Enqueue(_genRefRef = new GenRefRef(snapGen));
else if (_genRefRef.Gen != snapGen)
if (_genObj == null)
_genObjs.Enqueue(_genObj = new GenObj(snapGen));
else if (_genObj.Gen != snapGen)
throw new Exception("panic");
}
else
{
// not write-locked, can use latest gen, create a new gen ref
_genRefRefs.Enqueue(_genRefRef = new GenRefRef(_liveGen));
_genObjs.Enqueue(_genObj = new GenObj(_liveGen));
_nextGen = false; // this is the ONLY thing that triggers a _liveGen++
}
@@ -873,7 +876,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// - the genRefRef weak ref is dead because all snapshots have been collected
// in both cases, we will dequeue and collect
var snapshot = new Snapshot(this, _genRefRef.GetGenRef()
var snapshot = new Snapshot(this, _genObj.GetGenRef()
#if DEBUG
, _logger
#endif
@@ -930,10 +933,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
#if DEBUG
_logger.Debug<ContentStore>("Collect.");
#endif
while (_genRefRefs.TryPeek(out GenRefRef genRefRef) && (genRefRef.Count == 0 || genRefRef.WGenRef.IsAlive == false))
while (_genObjs.TryPeek(out var genObj) && (genObj.Count == 0 || genObj.WeakGenRef.IsAlive == false))
{
_genRefRefs.TryDequeue(out genRefRef); // cannot fail since TryPeek has succeeded
_floorGen = genRefRef.Gen;
_genObjs.TryDequeue(out genObj); // cannot fail since TryPeek has succeeded
_floorGen = genObj.Gen;
#if DEBUG
//_logger.Debug<ContentStore>("_floorGen=" + _floorGen + ", _liveGen=" + _liveGen);
#endif
@@ -1009,9 +1012,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
await task;
}
public long GenCount => _genRefRefs.Count;
public long GenCount => _genObjs.Count;
public long SnapCount => _genRefRefs.Sum(x => x.Count);
public long SnapCount => _genObjs.Sum(x => x.Count);
#endregion
@@ -1061,24 +1064,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
#region Classes
private class LinkedNode<TValue>
where TValue: class
{
public LinkedNode(TValue value, long gen, LinkedNode<TValue> next = null)
{
Value = value;
Gen = gen;
Next = next;
}
internal readonly long Gen;
// reading & writing references is thread-safe on all .NET platforms
// mark as volatile to ensure we always read the correct value
internal volatile TValue Value;
internal volatile LinkedNode<TValue> Next;
}
public class Snapshot : IDisposable
{
private readonly ContentStore _store;
@@ -1100,7 +1085,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
_store = store;
_genRef = genRef;
_gen = genRef.Gen;
Interlocked.Increment(ref genRef.GenRefRef.Count);
Interlocked.Increment(ref genRef.GenObj.Count);
//_thisCount = _count++;
#if DEBUG
@@ -1201,49 +1186,15 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
if (_gen < 0) return;
#if DEBUG
_logger.Debug<Snapshot>("Dispose snapshot ({Snapshot})", _genRef?.GenRefRef.Count.ToString() ?? "live");
_logger.Debug<Snapshot>("Dispose snapshot ({Snapshot})", _genRef?.GenObj.Count.ToString() ?? "live");
#endif
_gen = -1;
if (_genRef != null)
Interlocked.Decrement(ref _genRef.GenRefRef.Count);
Interlocked.Decrement(ref _genRef.GenObj.Count);
GC.SuppressFinalize(this);
}
}
internal class GenRefRef
{
public GenRefRef(long gen)
{
Gen = gen;
WGenRef = new WeakReference(null);
}
public GenRef GetGenRef()
{
// not thread-safe but always invoked from within a lock
var genRef = (GenRef) WGenRef.Target;
if (genRef == null)
WGenRef.Target = genRef = new GenRef(this, Gen);
return genRef;
}
public readonly long Gen;
public readonly WeakReference WGenRef;
public int Count;
}
internal class GenRef
{
public GenRef(GenRefRef genRefRef, long gen)
{
GenRefRef = genRefRef;
Gen = gen;
}
public readonly GenRefRef GenRefRef;
public readonly long Gen;
}
#endregion
}
}

View File

@@ -330,14 +330,15 @@ namespace Umbraco.Web.PublishedCache.NuCache
private void LockAndLoadContent(Action<IScope> action)
{
using (_contentStore.GetWriter(_scopeProvider))
// first get a writer, then a scope
// if there already is a scope, the writer will attach to it
// otherwise, it will only exist here - cheap
using (_contentStore.GetScopedWriteLock(_scopeProvider))
using (var scope = _scopeProvider.CreateScope())
{
using (var scope = _scopeProvider.CreateScope())
{
scope.ReadLock(Constants.Locks.ContentTree);
action(scope);
scope.Complete();
}
scope.ReadLock(Constants.Locks.ContentTree);
action(scope);
scope.Complete();
}
}
@@ -399,14 +400,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
private void LockAndLoadMedia(Action<IScope> action)
{
using (_mediaStore.GetWriter(_scopeProvider))
// see note in LockAndLoadContent
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
using (var scope = _scopeProvider.CreateScope())
{
using (var scope = _scopeProvider.CreateScope())
{
scope.ReadLock(Constants.Locks.MediaTree);
action(scope);
scope.Complete();
}
scope.ReadLock(Constants.Locks.MediaTree);
action(scope);
scope.Complete();
}
}
@@ -528,14 +528,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
private void LockAndLoadDomains()
{
using (_domainStore.GetWriter(_scopeProvider))
// see note in LockAndLoadContent
using (_domainStore.GetScopedWriteLock(_scopeProvider))
using (var scope = _scopeProvider.CreateScope())
{
using (var scope = _scopeProvider.CreateScope())
{
scope.ReadLock(Constants.Locks.Domains);
LoadDomainsLocked();
scope.Complete();
}
scope.ReadLock(Constants.Locks.Domains);
LoadDomainsLocked();
scope.Complete();
}
}
@@ -587,7 +586,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
return;
}
using (_contentStore.GetWriter(_scopeProvider))
using (_contentStore.GetScopedWriteLock(_scopeProvider))
{
NotifyLocked(payloads, out bool draftChanged2, out bool publishedChanged2);
draftChanged = draftChanged2;
@@ -683,7 +682,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
return;
}
using (_mediaStore.GetWriter(_scopeProvider))
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
{
NotifyLocked(payloads, out bool anythingChanged2);
anythingChanged = anythingChanged2;
@@ -804,7 +803,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (removedIds.Count == 0 && refreshedIds.Count == 0 && otherIds.Count == 0 && newIds.Count == 0) return;
using (store.GetWriter(_scopeProvider))
using (store.GetScopedWriteLock(_scopeProvider))
{
// ReSharper disable AccessToModifiedClosure
action(removedIds, refreshedIds, otherIds, newIds);
@@ -825,8 +824,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
payload.Removed ? "Removed" : "Refreshed",
payload.Id);
using (_contentStore.GetWriter(_scopeProvider))
using (_mediaStore.GetWriter(_scopeProvider))
using (_contentStore.GetScopedWriteLock(_scopeProvider))
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
{
// TODO: need to add a datatype lock
// this is triggering datatypes reload in the factory, and right after we create some
@@ -858,7 +857,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (_isReady == false)
return;
using (_domainStore.GetWriter(_scopeProvider))
// see note in LockAndLoadContent
using (_domainStore.GetScopedWriteLock(_scopeProvider))
{
foreach (var payload in payloads)
{

View File

@@ -0,0 +1,37 @@
using System;
using System.Threading;
namespace Umbraco.Web.PublishedCache.NuCache.Snap
{
internal class GenObj
{
public GenObj(long gen)
{
Gen = gen;
WeakGenRef = new WeakReference(null);
}
public GenRef GetGenRef()
{
// not thread-safe but always invoked from within a lock
var genRef = (GenRef)WeakGenRef.Target;
if (genRef == null)
WeakGenRef.Target = genRef = new GenRef(this);
return genRef;
}
public readonly long Gen;
public readonly WeakReference WeakGenRef;
public int Count;
public void Reference()
{
Interlocked.Increment(ref Count);
}
public void Release()
{
Interlocked.Decrement(ref Count);
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Umbraco.Web.PublishedCache.NuCache.Snap
{
internal class GenRef
{
public GenRef(GenObj genObj)
{
GenObj = genObj;
}
public readonly GenObj GenObj;
public long Gen => GenObj.Gen;
}
}

View File

@@ -0,0 +1,20 @@
namespace Umbraco.Web.PublishedCache.NuCache.Snap
{
internal class LinkedNode<TValue>
where TValue : class
{
public LinkedNode(TValue value, long gen, LinkedNode<TValue> next = null)
{
Value = value;
Gen = gen;
Next = next;
}
public readonly long Gen;
// reading & writing references is thread-safe on all .NET platforms
// mark as volatile to ensure we always read the correct value
public volatile TValue Value;
public volatile LinkedNode<TValue> Next;
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core.Scoping;
using Umbraco.Web.PublishedCache.NuCache.Snap;
namespace Umbraco.Web.PublishedCache.NuCache
{
@@ -20,9 +21,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
// This class is optimized for many readers, few writers
// Readers are lock-free
private readonly ConcurrentDictionary<TKey, LinkedNode> _items;
private readonly ConcurrentQueue<GenerationObject> _generationObjects;
private GenerationObject _generationObject;
private readonly ConcurrentDictionary<TKey, LinkedNode<TValue>> _items;
private readonly ConcurrentQueue<GenObj> _genObjs;
private GenObj _genObj;
private readonly object _wlocko = new object();
private readonly object _rlocko = new object();
private long _liveGen, _floorGen;
@@ -40,9 +41,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
public SnapDictionary()
{
_items = new ConcurrentDictionary<TKey, LinkedNode>();
_generationObjects = new ConcurrentQueue<GenerationObject>();
_generationObject = null; // no initial gen exists
_items = new ConcurrentDictionary<TKey, LinkedNode<TValue>>();
_genObjs = new ConcurrentQueue<GenObj>();
_genObj = null; // no initial gen exists
_liveGen = _floorGen = 0;
_nextGen = false; // first time, must create a snapshot
_collectAuto = true; // collect automatically by default
@@ -86,12 +87,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
// a scope contextual that represents a locked writer to the dictionary
private class SnapDictionaryWriter : ScopeContextualBase
private class ScopedWriteLock : ScopeContextualBase
{
private readonly WriteLockInfo _lockinfo = new WriteLockInfo();
private SnapDictionary<TKey, TValue> _dictionary;
private readonly SnapDictionary<TKey, TValue> _dictionary;
private int _released;
public SnapDictionaryWriter(SnapDictionary<TKey, TValue> dictionary, bool scoped)
public ScopedWriteLock(SnapDictionary<TKey, TValue> dictionary, bool scoped)
{
_dictionary = dictionary;
dictionary.Lock(_lockinfo, scoped);
@@ -99,17 +101,19 @@ namespace Umbraco.Web.PublishedCache.NuCache
public override void Release(bool completed)
{
if (_dictionary == null) return;
if (Interlocked.CompareExchange(ref _released, 1, 0) != 0)
return;
_dictionary.Release(_lockinfo, completed);
_dictionary = null;
}
}
// gets a scope contextual representing a locked writer to the dictionary
// GetScopedWriter? should the dict have a ref onto the scope provider?
public IDisposable GetWriter(IScopeProvider scopeProvider)
// the dict is write-locked until the write-lock is released
// which happens when it is disposed (non-scoped)
// or when the scope context exits (scoped)
public IDisposable GetScopedWriteLock(IScopeProvider scopeProvider)
{
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new SnapDictionaryWriter(this, scoped));
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped));
}
private void Lock(WriteLockInfo lockInfo, bool forceGen = false)
@@ -129,14 +133,18 @@ namespace Umbraco.Web.PublishedCache.NuCache
//RuntimeHelpers.PrepareConstrainedRegions();
try { } finally
{
// increment the lock count, and register that this lock is counting
_wlocked++;
lockInfo.Count = true;
if (_nextGen == false || (forceGen && _wlocked == 1)) // if true already... ok to have "holes" in generation objects
if (_nextGen == false || (forceGen && _wlocked == 1))
{
// because we are changing things, a new generation
// is created, which will trigger a new snapshot
_nextGen = true;
if (_nextGen)
_genObjs.Enqueue(_genObj = new GenObj(_liveGen));
_liveGen += 1;
_nextGen = true; // this is the ONLY place where _nextGen becomes true
}
}
}
@@ -153,6 +161,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
private void Release(WriteLockInfo lockInfo, bool commit = true)
{
// if the lock wasn't taken in the first place, do nothing
if (!lockInfo.Taken)
return;
if (commit == false)
{
var rtaken = false;
@@ -161,6 +173,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
Monitor.Enter(_rlocko, ref rtaken);
try { } finally
{
// forget about the temp. liveGen
_nextGen = false;
_liveGen -= 1;
}
@@ -183,8 +196,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
// decrement the lock count, if counting, then exit the lock
if (lockInfo.Count) _wlocked--;
if (lockInfo.Taken) Monitor.Exit(_wlocko);
Monitor.Exit(_wlocko);
}
private void Release(ReadLockInfo lockInfo)
@@ -198,9 +212,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
public int Count => _items.Count;
private LinkedNode GetHead(TKey key)
private LinkedNode<TValue> GetHead(TKey key)
{
_items.TryGetValue(key, out LinkedNode link); // else null
_items.TryGetValue(key, out var link); // else null
return link;
}
@@ -221,7 +235,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// for an older gen - if value is different then insert a new
// link for the new gen, with the new value
if (link.Value != value)
_items.TryUpdate(key, new LinkedNode(value, _liveGen, link), link);
_items.TryUpdate(key, new LinkedNode<TValue>(value, _liveGen, link), link);
}
else
{
@@ -235,7 +249,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
else
{
_items.TryAdd(key, new LinkedNode(value, _liveGen));
_items.TryAdd(key, new LinkedNode<TValue>(value, _liveGen));
}
}
finally
@@ -261,7 +275,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
if (kvp.Value.Gen < _liveGen)
{
var link = new LinkedNode(null, _liveGen, kvp.Value);
var link = new LinkedNode<TValue>(null, _liveGen, kvp.Value);
_items.TryUpdate(kvp.Key, link, kvp.Value);
}
else
@@ -337,12 +351,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
Lock(lockInfo);
// if no next generation is required, and we already have one,
// use it and create a new snapshot
if (_nextGen == false && _generationObject != null)
return new Snapshot(this, _generationObject.GetReference());
// if no next generation is required, and we already have a gen object,
// use it to create a new snapshot
if (_nextGen == false && _genObj != null)
return new Snapshot(this, _genObj.GetGenRef());
// else we need to try to create a new gen ref
// else we need to try to create a new gen object
// whether we are wlocked or not, noone can rlock while we do,
// so _liveGen and _nextGen are safe
if (_wlocked > 0) // volatile, cannot ++ but could --
@@ -350,29 +364,32 @@ namespace Umbraco.Web.PublishedCache.NuCache
// write-locked, cannot use latest gen (at least 1) so use previous
var snapGen = _nextGen ? _liveGen - 1 : _liveGen;
// create a new gen ref unless we already have it
if (_generationObject == null)
_generationObjects.Enqueue(_generationObject = new GenerationObject(snapGen));
else if (_generationObject.Gen != snapGen)
// create a new gen object if we don't already have one
// (happens the first time a snapshot is created)
if (_genObj == null)
_genObjs.Enqueue(_genObj = new GenObj(snapGen));
// if we have one already, ensure it's consistent
else if (_genObj.Gen != snapGen)
throw new Exception("panic");
}
else
{
// not write-locked, can use latest gen, create a new gen ref
_generationObjects.Enqueue(_generationObject = new GenerationObject(_liveGen));
// not write-locked, can use latest gen (_liveGen), create a corresponding new gen object
_genObjs.Enqueue(_genObj = new GenObj(_liveGen));
_nextGen = false; // this is the ONLY thing that triggers a _liveGen++
}
// so...
// the genRefRef has a weak ref to the genRef, and is queued
// the snapshot has a ref to the genRef, which has a ref to the genRefRef
// when the snapshot is disposed, it decreases genRefRef counter
// the genObj has a weak ref to the genRef, and is queued
// the snapshot has a ref to the genRef, which has a ref to the genObj
// when the snapshot is disposed, it decreases genObj counter
// so after a while, one of these conditions is going to be true:
// - the genRefRef counter is zero because all snapshots have properly been disposed
// - the genRefRef weak ref is dead because all snapshots have been collected
// - genObj.Count is zero because all snapshots have properly been disposed
// - genObj.WeakGenRef is dead because all snapshots have been collected
// in both cases, we will dequeue and collect
var snapshot = new Snapshot(this, _generationObject.GetReference());
var snapshot = new Snapshot(this, _genObj.GetGenRef());
// reading _floorGen is safe if _collectTask is null
if (_collectTask == null && _collectAuto && _liveGen - _floorGen > CollectMinGenDelta)
@@ -416,16 +433,16 @@ namespace Umbraco.Web.PublishedCache.NuCache
private void Collect()
{
// see notes in CreateSnapshot
while (_generationObjects.TryPeek(out GenerationObject generationObject) && (generationObject.Count == 0 || generationObject.WeakReference.IsAlive == false))
while (_genObjs.TryPeek(out var genObj) && (genObj.Count == 0 || genObj.WeakGenRef.IsAlive == false))
{
_generationObjects.TryDequeue(out generationObject); // cannot fail since TryPeek has succeeded
_floorGen = generationObject.Gen;
_genObjs.TryDequeue(out genObj); // cannot fail since TryPeek has succeeded
_floorGen = genObj.Gen;
}
Collect(_items);
}
private void Collect(ConcurrentDictionary<TKey, LinkedNode> dict)
private void Collect(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict)
{
// it is OK to enumerate a concurrent dictionary and it does not lock
// it - and here it's not an issue if we skip some items, they will be
@@ -460,7 +477,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// not live, null value, no next link = remove that one -- but only if
// the dict has not been updated, have to do it via ICollection<> (thanks
// Mr Toub) -- and if the dict has been updated there is nothing to collect
var idict = dict as ICollection<KeyValuePair<TKey, LinkedNode>>;
var idict = dict as ICollection<KeyValuePair<TKey, LinkedNode<TValue>>>;
/*var removed =*/ idict.Remove(kvp);
//Console.WriteLine("remove (" + (removed ? "true" : "false") + ")");
continue;
@@ -485,14 +502,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
task = _collectTask;
}
return task ?? Task.FromResult(0);
return task ?? Task.CompletedTask;
//if (task != null)
// await task;
}
public long GenCount => _generationObjects.Count;
public long GenCount => _genObjs.Count;
public long SnapCount => _generationObjects.Sum(x => x.Count);
public long SnapCount => _genObjs.Sum(x => x.Count);
#endregion
@@ -513,6 +530,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
public long LiveGen => _dict._liveGen;
public long FloorGen => _dict._floorGen;
public bool NextGen => _dict._nextGen;
public int WLocked => _dict._wlocked;
public bool CollectAuto
{
@@ -520,13 +538,15 @@ namespace Umbraco.Web.PublishedCache.NuCache
set => _dict._collectAuto = value;
}
public ConcurrentQueue<GenerationObject> GenerationObjects => _dict._generationObjects;
public GenObj GenObj => _dict._genObj;
public ConcurrentQueue<GenObj> GenObjs => _dict._genObjs;
public Snapshot LiveSnapshot => new Snapshot(_dict, _dict._liveGen);
public GenVal[] GetValues(TKey key)
{
_dict._items.TryGetValue(key, out LinkedNode link); // else null
_dict._items.TryGetValue(key, out var link); // else null
if (link == null)
return new GenVal[0];
@@ -559,35 +579,23 @@ namespace Umbraco.Web.PublishedCache.NuCache
#region Classes
private class LinkedNode
{
public LinkedNode(TValue value, long gen, LinkedNode next = null)
{
Value = value;
Gen = gen;
Next = next;
}
internal readonly long Gen;
// reading & writing references is thread-safe on all .NET platforms
// mark as volatile to ensure we always read the correct value
internal volatile TValue Value;
internal volatile LinkedNode Next;
}
public class Snapshot : IDisposable
{
private readonly SnapDictionary<TKey, TValue> _store;
private readonly GenerationReference _generationReference;
private long _gen; // copied for perfs
private readonly GenRef _genRef;
private readonly long _gen; // copied for perfs
private int _disposed;
internal Snapshot(SnapDictionary<TKey, TValue> store, GenerationReference generationReference)
//private static int _count;
//private readonly int _thisCount;
internal Snapshot(SnapDictionary<TKey, TValue> store, GenRef genRef)
{
_store = store;
_generationReference = generationReference;
_gen = generationReference.GenerationObject.Gen;
_generationReference.GenerationObject.Reference();
_genRef = genRef;
_gen = genRef.GenObj.Gen;
_genRef.GenObj.Reference();
//_thisCount = _count++;
}
internal Snapshot(SnapDictionary<TKey, TValue> store, long gen)
@@ -596,17 +604,21 @@ namespace Umbraco.Web.PublishedCache.NuCache
_gen = gen;
}
private void EnsureNotDisposed()
{
if (_disposed > 0)
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
}
public TValue Get(TKey key)
{
if (_gen < 0)
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
EnsureNotDisposed();
return _store.Get(key, _gen);
}
public IEnumerable<TValue> GetAll()
{
if (_gen < 0)
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
EnsureNotDisposed();
return _store.GetAll(_gen);
}
@@ -614,8 +626,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
get
{
if (_gen < 0)
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
EnsureNotDisposed();
return _store.IsEmpty(_gen);
}
}
@@ -624,63 +635,20 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
get
{
if (_gen < 0)
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
EnsureNotDisposed();
return _gen;
}
}
public void Dispose()
{
if (_gen < 0) return;
_gen = -1;
_generationReference?.GenerationObject.Release();
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
return;
_genRef?.GenObj.Release();
GC.SuppressFinalize(this);
}
}
internal class GenerationObject
{
public GenerationObject(long gen)
{
Gen = gen;
WeakReference = new WeakReference(null);
}
public GenerationReference GetReference()
{
// not thread-safe but always invoked from within a lock
var generationReference = (GenerationReference) WeakReference.Target;
if (generationReference == null)
WeakReference.Target = generationReference = new GenerationReference(this);
return generationReference;
}
public readonly long Gen;
public readonly WeakReference WeakReference;
public int Count;
public void Reference()
{
Interlocked.Increment(ref Count);
}
public void Release()
{
Interlocked.Decrement(ref Count);
}
}
internal class GenerationReference
{
public GenerationReference(GenerationObject generationObject)
{
GenerationObject = generationObject;
}
public readonly GenerationObject GenerationObject;
}
#endregion
}
}

View File

@@ -205,6 +205,9 @@
<Compile Include="Models\PublishedContent\HybridVariationContextAccessor.cs" />
<Compile Include="Models\TemplateQuery\QueryConditionExtensions.cs" />
<Compile Include="Mvc\SurfaceControllerTypeCollectionBuilder.cs" />
<Compile Include="PublishedCache\NuCache\Snap\GenObj.cs" />
<Compile Include="PublishedCache\NuCache\Snap\GenRef.cs" />
<Compile Include="PublishedCache\NuCache\Snap\LinkedNode.cs" />
<Compile Include="Routing\IPublishedRouter.cs" />
<Compile Include="Services\DashboardService.cs" />
<Compile Include="Services\IDashboardService.cs" />