NuCache+Scope - more SnapDictionary scoping

This commit is contained in:
Stephan
2017-07-14 12:51:57 +02:00
parent b0303ca753
commit ea72b0a7b6
4 changed files with 123 additions and 115 deletions

View File

@@ -9,15 +9,15 @@ namespace Umbraco.Core.Scoping
{
private bool _using, _scoped;
public static T Get<T>(IScopeProvider scopeProvider, string key, Func<T> ctor)
public static T Get<T>(IScopeProvider scopeProvider, string key, Func<bool, T> ctor)
where T : ScopeContextualBase
{
var scopeContext = scopeProvider.Context;
if (scopeContext == null)
return ctor();
return ctor(false);
var w = scopeContext.Enlist("ScopeContextualBase_" + key,
ctor,
() => ctor(true),
(completed, item) => { item.Release(completed); });
if (w._using) throw new InvalidOperationException("panic: used.");

View File

@@ -632,7 +632,7 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(1, d.Test.LiveGen);
Assert.IsTrue(d.Test.NextGen);
d.WriteLocked(() =>
using (d.GetWriter(GetScopeProvider()))
{
var s1 = d.CreateSnapshot();
@@ -640,7 +640,7 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(1, d.Test.LiveGen);
Assert.IsTrue(d.Test.NextGen);
Assert.IsNull(s1.Get(1));
});
}
var s2 = d.CreateSnapshot();
@@ -685,7 +685,7 @@ namespace Umbraco.Tests.Cache
Assert.IsFalse(d.Test.NextGen);
Assert.AreEqual("uno", s2.Get(1));
d.WriteLocked(() =>
using (d.GetWriter(GetScopeProvider()))
{
// gen 3
Assert.AreEqual(2, d.Test.GetValues(1).Length);
@@ -701,7 +701,7 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsTrue(d.Test.NextGen); // has NOT changed when (non) creating snapshot
Assert.AreEqual("uno", s3.Get(1));
});
}
var s4 = d.CreateSnapshot();
@@ -717,13 +717,14 @@ namespace Umbraco.Tests.Cache
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
d.WriteLocked(() =>
var scopeProvider = GetScopeProvider();
using (d.GetWriter(scopeProvider))
{
d.WriteLocked(() =>
using (d.GetWriter(scopeProvider))
{
d.Set(1, "one");
});
});
}
}
}
[Test]
@@ -761,9 +762,7 @@ namespace Umbraco.Tests.Cache
Assert.IsFalse(d.Test.NextGen);
Assert.AreEqual("uno", s2.Get(1));
var scopeProviderMock = new Mock<IScopeProvider>();
scopeProviderMock.Setup(x => x.Context).Returns<ScopeContext>(null);
var scopeProvider = scopeProviderMock.Object;
var scopeProvider = GetScopeProvider();
using (d.GetWriter(scopeProvider))
{
@@ -808,9 +807,7 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s2.Gen);
Assert.AreEqual("uno", s2.Get(1));
var scopeProviderMock = new Mock<IScopeProvider>();
scopeProviderMock.Setup(x => x.Context).Returns<ScopeContext>(null);
var scopeProvider = scopeProviderMock.Object;
var scopeProvider = GetScopeProvider();
using (d.GetWriter(scopeProvider))
{
@@ -849,10 +846,7 @@ 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;
var scopeProvider = GetScopeProvider(true);
using (d.GetWriter(scopeProvider))
{
@@ -873,7 +867,7 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual(2, s4.Gen);
Assert.AreEqual("uno", s4.Get(1));
scopeContext.ScopeExit(true);
((ScopeContext) scopeProvider.Context).ScopeExit(true);
var s5 = d.CreateSnapshot();
Assert.AreEqual(3, s5.Gen);
@@ -960,5 +954,14 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual("uno", all[0]);
Assert.AreEqual("four", all[3]);
}
private IScopeProvider GetScopeProvider(bool withContext = false)
{
var scopeProviderMock = new Mock<IScopeProvider>();
var scopeContext = withContext ? new ScopeContext() : null;
scopeProviderMock.Setup(x => x.Context).Returns(scopeContext);
var scopeProvider = scopeProviderMock.Object;
return scopeProvider;
}
}
}

View File

@@ -427,7 +427,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
private void LockAndLoadDomains()
{
_domainStore.WriteLocked(() =>
using (_domainStore.GetWriter(_scopeProvider))
{
using (var uow = _uowProvider.CreateUnitOfWork())
{
@@ -435,7 +435,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
LoadDomainsLocked();
uow.Complete();
}
});
}
}
private void LoadDomainsLocked()
@@ -758,7 +758,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (_isReady == false)
return;
_domainStore.WriteLocked(() =>
using (_domainStore.GetWriter(_scopeProvider))
{
foreach (var payload in payloads)
{
@@ -787,7 +787,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
break;
}
}
});
}
}
#endregion

View File

@@ -53,6 +53,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
#region Locking
// read and write locks are not exclusive
// it is not possible to write-lock while someone is read-locked
// it is possible to read-lock while someone is write-locked
//
// so when getting a read-lock,
// either we are write-locked or not, but if not, we won't be write-locked
// otoh the write-lock may be released in the meantime
// Lock has a 'forceGen' parameter:
// used to start a set of changes that may not commit, to isolate the set from any pending
// changes that would not have been snapshotted yet, so they cannot be rolled back by accident
@@ -67,43 +75,47 @@ namespace Umbraco.Web.PublishedCache.NuCache
private readonly string _instanceId = Guid.NewGuid().ToString("N");
// a scope contextual that represents a locked writer to the dictionary
private class SnapDictionaryWriter : ScopeContextualBase
private class ReadLockInfo
{
private SnapDictionary<TKey, TValue> _dictionary;
private WriteLock _wl;
public SnapDictionaryWriter(SnapDictionary<TKey, TValue> dictionary)
{
_dictionary = dictionary;
_wl = new WriteLock();
dictionary.Lock(_wl, true);
}
public override void Release(bool completed)
{
if (_wl == null) return;
_dictionary.Release(_wl, completed);
_wl = null;
_dictionary = null;
}
public bool Taken;
}
// gets a scope contextual representing a locked writer to the dictionary
public IDisposable GetWriter(IScopeProvider scopeProvider)
{
return ScopeContextualBase.Get(scopeProvider, _instanceId, () => new SnapDictionaryWriter(this));
}
private class WriteLock
private class WriteLockInfo
{
public bool Taken;
public bool Count;
}
private void Lock(WriteLock wl, bool forceGen)
// a scope contextual that represents a locked writer to the dictionary
private class SnapDictionaryWriter : ScopeContextualBase
{
Monitor.Enter(_wlocko, ref wl.Taken);
private readonly WriteLockInfo _lockinfo = new WriteLockInfo();
private SnapDictionary<TKey, TValue> _dictionary;
public SnapDictionaryWriter(SnapDictionary<TKey, TValue> dictionary, bool scoped)
{
_dictionary = dictionary;
dictionary.Lock(_lockinfo, scoped);
}
public override void Release(bool completed)
{
if (_dictionary == null) return;
_dictionary.Release(_lockinfo, completed);
_dictionary = null;
}
}
// gets a scope contextual representing a locked writer to the dictionary
// fixme GetScopedWriter? should the dict have a ref onto the scope provider?
public IDisposable GetWriter(IScopeProvider scopeProvider)
{
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new SnapDictionaryWriter(this, scoped));
}
private void Lock(WriteLockInfo lockInfo, bool forceGen = false)
{
Monitor.Enter(_wlocko, ref lockInfo.Taken);
var rtaken = false;
try
@@ -116,12 +128,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
// http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas/
// http://chabster.blogspot.fr/2013/12/readerwriterlockslim-fails-on-dual.html
//RuntimeHelpers.PrepareConstrainedRegions();
try
{ }
finally
try { } finally
{
_wlocked++;
wl.Count = true;
lockInfo.Count = true;
if (_nextGen == false || (forceGen && _wlocked == 1)) // if true already... ok to have "holes" in generation objects
{
// because we are changing things, a new generation
@@ -137,12 +147,29 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
private void Release(WriteLock wl, bool commit = true)
private void Lock(ReadLockInfo lockInfo)
{
Monitor.Enter(_rlocko, ref lockInfo.Taken);
}
private void Release(WriteLockInfo lockInfo, bool commit = true)
{
if (commit == false)
{
_nextGen = false;
_liveGen -= 1;
var rtaken = false;
try
{
Monitor.Enter(_rlocko, ref rtaken);
try { } finally
{
_nextGen = false;
_liveGen -= 1;
}
}
finally
{
if (rtaken) Monitor.Exit(_rlocko);
}
foreach (var item in _items)
{
@@ -157,56 +184,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
if (wl.Count) _wlocked--;
if (wl.Taken) Monitor.Exit(_wlocko);
if (lockInfo.Count) _wlocked--;
if (lockInfo.Taken) Monitor.Exit(_wlocko);
}
public void WriteLocked(Action action)
private void Release(ReadLockInfo lockInfo)
{
var wl = new WriteLock();
try
{
// lock (again) - don't force a next gen if there's already one
Lock(wl, false);
action();
}
finally
{
Release(wl);
}
}
public T WriteLocked<T>(Func<T> func)
{
var wl = new WriteLock();
try
{
// lock (again) - don't force a next gen if there's already one
Lock(wl, false);
return func();
}
finally
{
Release(wl);
}
}
private T ReadLocked<T>(Func<bool, T> func)
{
var rtaken = false;
try
{
Monitor.Enter(_rlocko, ref rtaken);
// we have rlock, so it cannot ++
// it could -- though, so... volatile
var wlocked = _wlocked > 0;
return func(wlocked);
}
finally
{
if (rtaken) Monitor.Exit(_rlocko);
}
if (lockInfo.Taken) Monitor.Exit(_rlocko);
}
#endregion
@@ -223,8 +207,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
public void Set(TKey key, TValue value)
{
WriteLocked(() =>
var lockInfo = new WriteLockInfo();
try
{
Lock(lockInfo);
// this is safe only because we're write-locked
var link = GetHead(key);
if (link != null)
@@ -251,7 +238,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
_items.TryAdd(key, new LinkedNode(value, _liveGen));
}
});
}
finally
{
Release(lockInfo);
}
}
public void Clear(TKey key)
@@ -261,8 +252,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
public void Clear()
{
WriteLocked(() =>
var lockInfo = new WriteLockInfo();
try
{
Lock(lockInfo);
// this is safe only because we're write-locked
foreach (var kvp in _items.Where(x => x.Value != null))
{
@@ -276,7 +270,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
kvp.Value.Value = null;
}
}
});
}
finally
{
Release(lockInfo);
}
}
public TValue Get(TKey key, long gen)
@@ -335,8 +333,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
public Snapshot CreateSnapshot()
{
return ReadLocked(wlocked =>
var lockInfo = new ReadLockInfo();
try
{
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)
@@ -345,7 +346,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// else we need to try to create a new gen ref
// whether we are wlocked or not, noone can rlock while we do,
// so _liveGen and _nextGen are safe
if (wlocked)
if (_wlocked > 0) // volatile, cannot ++ but could --
{
// write-locked, cannot use latest gen (at least 1) so use previous
var snapGen = _nextGen ? _liveGen - 1 : _liveGen;
@@ -379,7 +380,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
CollectAsyncLocked();
return snapshot;
});
}
finally
{
Release(lockInfo);
}
}
public Task CollectAsync()