Removes ability to have recursive locks in SnapDictionary, changes logic to require locking around the methods just like ContentStore, updates tests

This commit is contained in:
Shannon
2020-01-03 15:04:39 +11:00
parent 8e3b3c8326
commit 243e76b3cc
4 changed files with 204 additions and 194 deletions

View File

@@ -9,6 +9,47 @@ using Umbraco.Web.PublishedCache.NuCache.Snap;
namespace Umbraco.Tests.Cache
{
/// <summary>
/// Used for tests
/// </summary>
public static class SnapDictionaryExtensions
{
internal static void Set<TKey, TValue>(this SnapDictionary<TKey, TValue> d, TKey key, TValue value)
where TValue : class
{
using (d.GetScopedWriteLock(GetScopeProvider()))
{
d.SetLocked(key, value);
}
}
internal static void Clear<TKey, TValue>(this SnapDictionary<TKey, TValue> d)
where TValue : class
{
using (d.GetScopedWriteLock(GetScopeProvider()))
{
d.ClearLocked();
}
}
internal static void Clear<TKey, TValue>(this SnapDictionary<TKey, TValue> d, TKey key)
where TValue : class
{
using (d.GetScopedWriteLock(GetScopeProvider()))
{
d.ClearLocked(key);
}
}
private static IScopeProvider GetScopeProvider()
{
var scopeProvider = Mock.Of<IScopeProvider>();
Mock.Get(scopeProvider)
.Setup(x => x.Context).Returns(() => null);
return scopeProvider;
}
}
[TestFixture]
public class SnapDictionaryTests
{
@@ -223,7 +264,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
// gen 1
d.Set(1, "one");
Assert.AreEqual(1, d.Test.GetValues(1).Length);
@@ -321,7 +362,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
Assert.AreEqual(0, d.Test.GetValues(1).Length);
// gen 1
@@ -416,7 +457,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
// gen 1
d.Set(1, "one");
Assert.AreEqual(1, d.Test.GetValues(1).Length);
@@ -578,7 +619,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
d.Set(1, "one");
d.Set(2, "two");
@@ -689,7 +730,7 @@ namespace Umbraco.Tests.Cache
{
// gen 3
Assert.AreEqual(2, d.Test.GetValues(1).Length);
d.Set(1, "ein");
d.SetLocked(1, "ein");
Assert.AreEqual(3, d.Test.GetValues(1).Length);
Assert.AreEqual(3, d.Test.LiveGen);
@@ -727,31 +768,25 @@ namespace Umbraco.Tests.Cache
using (var w1 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider))
Assert.Throws<InvalidOperationException>(() =>
{
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);
}
using (var w2 = d.GetScopedWriteLock(scopeProvider))
{
}
});
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(0, d.CreateSnapshot().Gen);
}
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(0, t.WLocked);
Assert.IsFalse(t.IsLocked);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, d.CreateSnapshot().Gen);
@@ -772,11 +807,14 @@ namespace Umbraco.Tests.Cache
using (var w1 = d.GetScopedWriteLock(scopeProvider))
{
// This one is interesting, although we don't allow recursive locks, since this is
// using the same ScopeContext/key, the lock acquisition is only done once
using (var w2 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreSame(w1, w2);
d.Set(1, "one");
d.SetLocked(1, "one");
}
}
}
@@ -797,19 +835,16 @@ namespace Umbraco.Tests.Cache
using (var w1 = d.GetScopedWriteLock(scopeProvider1))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider2))
Assert.Throws<InvalidOperationException>(() =>
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(2, t.WLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider2))
{
}
});
Assert.AreNotSame(w1, w2);
d.Set(1, "one");
}
}
}
@@ -848,13 +883,13 @@ namespace Umbraco.Tests.Cache
Assert.IsFalse(d.Test.NextGen);
Assert.AreEqual("uno", s2.Get(1));
var scopeProvider = GetScopeProvider();
var scopeProvider = GetScopeProvider();
using (d.GetScopedWriteLock(scopeProvider))
{
// gen 3
Assert.AreEqual(2, d.Test.GetValues(1).Length);
d.Set(1, "ein");
d.SetLocked(1, "ein");
Assert.AreEqual(3, d.Test.GetValues(1).Length);
Assert.AreEqual(3, d.Test.LiveGen);
@@ -881,6 +916,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
// gen 1
d.Set(1, "one");
@@ -894,12 +930,11 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual("uno", s2.Get(1));
var scopeProvider = GetScopeProvider();
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
d.Set(1, "ein");
d.SetLocked(1, "ein");
var s3 = d.CreateSnapshot();
Assert.AreEqual(2, s3.Gen);
Assert.AreEqual("uno", s3.Get(1));
@@ -934,12 +969,11 @@ namespace Umbraco.Tests.Cache
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
d.Set(1, "ein");
d.SetLocked(1, "ein");
var s3 = d.CreateSnapshot();
Assert.AreEqual(2, s3.Gen);
Assert.AreEqual("uno", s3.Get(1));
@@ -967,7 +1001,7 @@ namespace Umbraco.Tests.Cache
var d = new SnapDictionary<int, string>();
var t = d.Test;
t.CollectAuto = false;
// gen 1
d.Set(1, "one");
var s1 = d.CreateSnapshot();
@@ -984,12 +1018,11 @@ namespace Umbraco.Tests.Cache
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
d.Set(1, "ein");
d.SetLocked(1, "ein");
var s3 = d.CreateSnapshot();
Assert.AreEqual(2, s3.Gen);
Assert.AreEqual("uno", s3.Get(1));
@@ -997,7 +1030,7 @@ namespace Umbraco.Tests.Cache
// we made some changes, so a next gen is required
Assert.AreEqual(3, t.LiveGen);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
// but live snapshot contains changes
var ls = t.LiveSnapshot;
@@ -1008,7 +1041,7 @@ namespace Umbraco.Tests.Cache
// nothing is committed until scope exits
Assert.AreEqual(3, t.LiveGen);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
// no changes until exit
var s4 = d.CreateSnapshot();
@@ -1020,7 +1053,7 @@ namespace Umbraco.Tests.Cache
// now things have changed
Assert.AreEqual(2, t.LiveGen);
Assert.IsFalse(t.NextGen);
Assert.AreEqual(0, t.WLocked);
Assert.IsFalse(t.IsLocked);
// no changes since not completed
var s5 = d.CreateSnapshot();
@@ -1097,9 +1130,10 @@ namespace Umbraco.Tests.Cache
// 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");
d.SetLocked(1, "ein");
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsNotNull(d.Test.GenObj);
@@ -1107,7 +1141,7 @@ namespace Umbraco.Tests.Cache
}
// writer has not released
Assert.AreEqual(1, d.Test.WLocked);
Assert.IsTrue(d.Test.IsLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
@@ -1118,7 +1152,7 @@ namespace Umbraco.Tests.Cache
// panic!
var s2 = d.CreateSnapshot();
Assert.AreEqual(1, d.Test.WLocked);
Assert.IsTrue(d.Test.IsLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
@@ -1127,7 +1161,7 @@ namespace Umbraco.Tests.Cache
// release writer
scopeContext.ScopeExit(true);
Assert.AreEqual(0, d.Test.WLocked);
Assert.IsFalse(d.Test.IsLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
@@ -1135,7 +1169,7 @@ namespace Umbraco.Tests.Cache
var s3 = d.CreateSnapshot();
Assert.AreEqual(0, d.Test.WLocked);
Assert.IsFalse(d.Test.IsLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(3, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);

View File

@@ -39,7 +39,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
private long _liveGen, _floorGen;
private bool _nextGen, _collectAuto;
private Task _collectTask;
private volatile int _wlocked;
private List<KeyValuePair<int, ContentNodeKit>> _wchanges;
// TODO: collection trigger (ok for now)
@@ -83,7 +82,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
private class WriteLockInfo
{
public bool Taken;
public bool Count;
}
// a scope contextual that represents a locked writer to the dictionary
@@ -111,7 +109,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
// TODO: GetScopedWriter? should the dict have a ref onto the scope provider?
public IDisposable GetScopedWriteLock(IScopeProvider scopeProvider)
{
_logger.Debug<ContentStore>("GetScopedWriteLock");
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped));
}
@@ -123,6 +120,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
private void Lock(WriteLockInfo lockInfo, bool forceGen = false)
{
if (Monitor.IsEntered(_wlocko))
throw new InvalidOperationException("Recursive locks not allowed");
Monitor.Enter(_wlocko, ref lockInfo.Taken);
lock(_rlocko)
@@ -131,9 +131,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
try { }
finally
{
_wlocked++;
lockInfo.Count = true;
if (_nextGen == false || (forceGen && _wlocked == 1))
if (_nextGen == false || (forceGen))
{
// because we are changing things, a new generation
// is created, which will trigger a new snapshot
@@ -179,12 +177,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
_localDb.Commit();
}
// TODO: Shouldn't this be in a finally block?
// TODO: Shouldn't this be decremented after we exit??
// TODO: Shouldn't the locked flag never exceed 1?
if (lockInfo.Count)
_wlocked--;
// TODO: Shouldn't this be in a finally block?
if (lockInfo.Taken)
Monitor.Exit(_wlocko);
@@ -220,22 +212,18 @@ namespace Umbraco.Web.PublishedCache.NuCache
public void ReleaseLocalDb()
{
_logger.Info<ContentStore>("Releasing DB...");
var lockInfo = new WriteLockInfo();
try
{
try
{
// Trying to lock could throw exceptions so always make sure to clean up.
_logger.Info<ContentStore>("Trying to lock before releasing DB (lock count = {LockCount}) ...", _wlocked);
// Trying to lock could throw exceptions so always make sure to clean up.
Lock(lockInfo);
}
finally
{
try
{
_logger.Info<ContentStore>("Disposing local DB...");
_localDb?.Dispose();
}
catch (Exception ex)
@@ -275,57 +263,61 @@ namespace Umbraco.Web.PublishedCache.NuCache
#region Content types
public void NewContentTypes(IEnumerable<IPublishedContentType> types)
/// <summary>
/// Sets data for new content types
/// </summary>
/// <param name="types"></param>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public void NewContentTypesLocked(IEnumerable<IPublishedContentType> types)
{
var lockInfo = new WriteLockInfo();
try
{
_logger.Debug<ContentStore>("NewContentTypes");
Lock(lockInfo);
EnsureLocked();
foreach (var type in types)
{
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
}
}
finally
foreach (var type in types)
{
Release(lockInfo);
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
}
}
public void UpdateContentTypes(IEnumerable<IPublishedContentType> types)
/// <summary>
/// Sets data for updated content types
/// </summary>
/// <param name="types"></param>
/// <remarks>
/// This methods MUST be called from within a write lock, normally wrapped within GetScopedWriteLock
/// otherwise an exception will occur.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if this method is not called within a write lock
/// </exception>
public void UpdateContentTypesLocked(IEnumerable<IPublishedContentType> types)
{
//nothing to do if this is empty, no need to lock/allocate/iterate/etc...
if (!types.Any()) return;
var lockInfo = new WriteLockInfo();
try
EnsureLocked();
var index = types.ToDictionary(x => x.Id, x => x);
foreach (var type in index.Values)
{
_logger.Debug<ContentStore>("UpdateContentTypes");
Lock(lockInfo);
var index = types.ToDictionary(x => x.Id, x => x);
foreach (var type in index.Values)
{
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
}
foreach (var link in _contentNodes.Values)
{
var node = link.Value;
if (node == null) continue;
var contentTypeId = node.ContentType.Id;
if (index.TryGetValue(contentTypeId, out var contentType) == false) continue;
SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType));
}
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
}
finally
foreach (var link in _contentNodes.Values)
{
Release(lockInfo);
var node = link.Value;
if (node == null) continue;
var contentTypeId = node.ContentType.Id;
if (index.TryGetValue(contentTypeId, out var contentType) == false) continue;
SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType));
}
}
@@ -1256,7 +1248,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 > 0) // volatile, cannot ++ but could --
if (Monitor.IsEntered(_wlocko))
{
// write-locked, cannot use latest gen (at least 1) so use previous
var snapGen = _nextGen ? _liveGen - 1 : _liveGen;

View File

@@ -622,7 +622,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
.Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false)
.Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard)))
{
_domainStore.Set(domain.Id, domain);
_domainStore.SetLocked(domain.Id, domain);
}
}
@@ -980,7 +980,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
break;
case DomainChangeTypes.Remove:
_domainStore.Clear(payload.Id);
_domainStore.ClearLocked(payload.Id);
break;
case DomainChangeTypes.Refresh:
var domain = _serviceContext.DomainService.GetById(payload.Id);
@@ -988,7 +988,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (domain.RootContentId.HasValue == false) continue; // anomaly
if (domain.LanguageIsoCode.IsNullOrWhiteSpace()) continue; // anomaly
var culture = CultureInfo.GetCultureInfo(domain.LanguageIsoCode);
_domainStore.Set(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard));
_domainStore.SetLocked(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard));
break;
}
}
@@ -1075,9 +1075,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
_contentStore.UpdateContentTypes(removedIds, typesA, kits);
if (!otherIds.IsCollectionEmpty())
_contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()));
_contentStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()));
if (!newIds.IsCollectionEmpty())
_contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()));
_contentStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()));
scope.Complete();
}
}
@@ -1106,9 +1106,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
_mediaStore.UpdateContentTypes(removedIds, typesA, kits);
if (!otherIds.IsCollectionEmpty())
_mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
_mediaStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
if (!newIds.IsCollectionEmpty())
_mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray());
_mediaStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray());
scope.Complete();
}
}

View File

@@ -35,7 +35,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
private long _liveGen, _floorGen;
private bool _nextGen, _collectAuto;
private Task _collectTask;
private volatile int _wlocked;
// minGenDelta to be adjusted
// we may want to throttle collects even if delta is reached
@@ -84,7 +83,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
private class WriteLockInfo
{
public bool Taken;
public bool Count;
}
// a scope contextual that represents a locked writer to the dictionary
@@ -92,8 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
private readonly WriteLockInfo _lockinfo = new WriteLockInfo();
private readonly SnapDictionary<TKey, TValue> _dictionary;
private int _released;
public ScopedWriteLock(SnapDictionary<TKey, TValue> dictionary, bool scoped)
{
_dictionary = dictionary;
@@ -102,8 +99,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
public override void Release(bool completed)
{
if (Interlocked.CompareExchange(ref _released, 1, 0) != 0)
return;
_dictionary.Release(_lockinfo, completed);
}
}
@@ -117,8 +112,17 @@ namespace Umbraco.Web.PublishedCache.NuCache
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped));
}
private void EnsureLocked()
{
if (!Monitor.IsEntered(_wlocko))
throw new InvalidOperationException("Write lock must be acquried.");
}
private void Lock(WriteLockInfo lockInfo, bool forceGen = false)
{
if (Monitor.IsEntered(_wlocko))
throw new InvalidOperationException("Recursive locks not allowed");
Monitor.Enter(_wlocko, ref lockInfo.Taken);
lock(_rlocko)
@@ -132,11 +136,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
try { }
finally
{
// increment the lock count, and register that this lock is counting
_wlocked++;
lockInfo.Count = true;
if (_nextGen == false || (forceGen && _wlocked == 1))
if (_nextGen == false || (forceGen))
{
// because we are changing things, a new generation
// is created, which will trigger a new snapshot
@@ -181,8 +181,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
// decrement the lock count, if counting, then exit the lock
if (lockInfo.Count) _wlocked--;
// TODO: Shouldn't this be in a finally block?
Monitor.Exit(_wlocko);
}
@@ -198,75 +197,59 @@ namespace Umbraco.Web.PublishedCache.NuCache
return link;
}
public void Set(TKey key, TValue value)
public void SetLocked(TKey key, TValue value)
{
var lockInfo = new WriteLockInfo();
try
{
Lock(lockInfo);
EnsureLocked();
// this is safe only because we're write-locked
var link = GetHead(key);
if (link != null)
// this is safe only because we're write-locked
var link = GetHead(key);
if (link != null)
{
// already in the dict
if (link.Gen != _liveGen)
{
// already in the dict
if (link.Gen != _liveGen)
{
// 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<TValue>(value, _liveGen, link), link);
}
else
{
// for the live gen - we can fix the live gen - and remove it
// if value is null and there's no next gen
if (value == null && link.Next == null)
_items.TryRemove(key, out link);
else
link.Value = value;
}
// 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<TValue>(value, _liveGen, link), link);
}
else
{
_items.TryAdd(key, new LinkedNode<TValue>(value, _liveGen));
}
}
finally
{
Release(lockInfo);
}
}
public void Clear(TKey key)
{
Set(key, null);
}
public void Clear()
{
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))
{
if (kvp.Value.Gen < _liveGen)
{
var link = new LinkedNode<TValue>(null, _liveGen, kvp.Value);
_items.TryUpdate(kvp.Key, link, kvp.Value);
}
// for the live gen - we can fix the live gen - and remove it
// if value is null and there's no next gen
if (value == null && link.Next == null)
_items.TryRemove(key, out link);
else
{
kvp.Value.Value = null;
}
link.Value = value;
}
}
finally
else
{
Release(lockInfo);
_items.TryAdd(key, new LinkedNode<TValue>(value, _liveGen));
}
}
public void ClearLocked(TKey key)
{
SetLocked(key, null);
}
public void ClearLocked()
{
EnsureLocked();
// this is safe only because we're write-locked
foreach (var kvp in _items.Where(x => x.Value != null))
{
if (kvp.Value.Gen < _liveGen)
{
var link = new LinkedNode<TValue>(null, _liveGen, kvp.Value);
_items.TryUpdate(kvp.Key, link, kvp.Value);
}
else
{
kvp.Value.Value = null;
}
}
}
@@ -336,7 +319,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// 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 --
if (Monitor.IsEntered(_wlocko))
{
// write-locked, cannot use latest gen (at least 1) so use previous
var snapGen = _nextGen ? _liveGen - 1 : _liveGen;
@@ -468,17 +451,18 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
public /*async*/ Task PendingCollect()
{
Task task;
lock (_rlocko)
{
task = _collectTask;
}
return task ?? Task.CompletedTask;
//if (task != null)
// await task;
}
// TODO: This is never used? Should it be? Maybe move to TestHelper below?
//public /*async*/ Task PendingCollect()
//{
// Task task;
// lock (_rlocko)
// {
// task = _collectTask;
// }
// return task ?? Task.CompletedTask;
// //if (task != null)
// // await task;
//}
public long GenCount => _genObjs.Count;
@@ -503,7 +487,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 IsLocked => Monitor.IsEntered(_dict._wlocko);
public bool CollectAuto
{