Revert "Merge remote-tracking branch 'origin/v8/8.0' into temp8-4011"
This reverts commite73cf590b7, reversing changes made to89903a633b.
This commit is contained in:
@@ -5,7 +5,6 @@ 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
|
||||
{
|
||||
@@ -21,9 +20,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
// This class is optimized for many readers, few writers
|
||||
// Readers are lock-free
|
||||
|
||||
private readonly ConcurrentDictionary<TKey, LinkedNode<TValue>> _items;
|
||||
private readonly ConcurrentQueue<GenObj> _genObjs;
|
||||
private GenObj _genObj;
|
||||
private readonly ConcurrentDictionary<TKey, LinkedNode> _items;
|
||||
private readonly ConcurrentQueue<GenerationObject> _generationObjects;
|
||||
private GenerationObject _generationObject;
|
||||
private readonly object _wlocko = new object();
|
||||
private readonly object _rlocko = new object();
|
||||
private long _liveGen, _floorGen;
|
||||
@@ -41,9 +40,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
public SnapDictionary()
|
||||
{
|
||||
_items = new ConcurrentDictionary<TKey, LinkedNode<TValue>>();
|
||||
_genObjs = new ConcurrentQueue<GenObj>();
|
||||
_genObj = null; // no initial gen exists
|
||||
_items = new ConcurrentDictionary<TKey, LinkedNode>();
|
||||
_generationObjects = new ConcurrentQueue<GenerationObject>();
|
||||
_generationObject = null; // no initial gen exists
|
||||
_liveGen = _floorGen = 0;
|
||||
_nextGen = false; // first time, must create a snapshot
|
||||
_collectAuto = true; // collect automatically by default
|
||||
@@ -87,13 +86,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
|
||||
// a scope contextual that represents a locked writer to the dictionary
|
||||
private class ScopedWriteLock : ScopeContextualBase
|
||||
private class SnapDictionaryWriter : ScopeContextualBase
|
||||
{
|
||||
private readonly WriteLockInfo _lockinfo = new WriteLockInfo();
|
||||
private readonly SnapDictionary<TKey, TValue> _dictionary;
|
||||
private int _released;
|
||||
private SnapDictionary<TKey, TValue> _dictionary;
|
||||
|
||||
public ScopedWriteLock(SnapDictionary<TKey, TValue> dictionary, bool scoped)
|
||||
public SnapDictionaryWriter(SnapDictionary<TKey, TValue> dictionary, bool scoped)
|
||||
{
|
||||
_dictionary = dictionary;
|
||||
dictionary.Lock(_lockinfo, scoped);
|
||||
@@ -101,19 +99,17 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
public override void Release(bool completed)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _released, 1, 0) != 0)
|
||||
return;
|
||||
if (_dictionary == null) return;
|
||||
_dictionary.Release(_lockinfo, completed);
|
||||
_dictionary = null;
|
||||
}
|
||||
}
|
||||
|
||||
// gets a scope contextual representing a locked writer to the dictionary
|
||||
// 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)
|
||||
// GetScopedWriter? should the dict have a ref onto the scope provider?
|
||||
public IDisposable GetWriter(IScopeProvider scopeProvider)
|
||||
{
|
||||
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped));
|
||||
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new SnapDictionaryWriter(this, scoped));
|
||||
}
|
||||
|
||||
private void Lock(WriteLockInfo lockInfo, bool forceGen = false)
|
||||
@@ -133,18 +129,14 @@ 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 (_nextGen == false || (forceGen && _wlocked == 1)) // if true already... ok to have "holes" in generation objects
|
||||
{
|
||||
// because we are changing things, a new generation
|
||||
// is created, which will trigger a new snapshot
|
||||
if (_nextGen)
|
||||
_genObjs.Enqueue(_genObj = new GenObj(_liveGen));
|
||||
_nextGen = true;
|
||||
_liveGen += 1;
|
||||
_nextGen = true; // this is the ONLY place where _nextGen becomes true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,10 +153,6 @@ 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;
|
||||
@@ -173,7 +161,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
Monitor.Enter(_rlocko, ref rtaken);
|
||||
try { } finally
|
||||
{
|
||||
// forget about the temp. liveGen
|
||||
_nextGen = false;
|
||||
_liveGen -= 1;
|
||||
}
|
||||
@@ -196,9 +183,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
}
|
||||
|
||||
// decrement the lock count, if counting, then exit the lock
|
||||
if (lockInfo.Count) _wlocked--;
|
||||
Monitor.Exit(_wlocko);
|
||||
if (lockInfo.Taken) Monitor.Exit(_wlocko);
|
||||
}
|
||||
|
||||
private void Release(ReadLockInfo lockInfo)
|
||||
@@ -212,9 +198,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
public int Count => _items.Count;
|
||||
|
||||
private LinkedNode<TValue> GetHead(TKey key)
|
||||
private LinkedNode GetHead(TKey key)
|
||||
{
|
||||
_items.TryGetValue(key, out var link); // else null
|
||||
_items.TryGetValue(key, out LinkedNode link); // else null
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -235,7 +221,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<TValue>(value, _liveGen, link), link);
|
||||
_items.TryUpdate(key, new LinkedNode(value, _liveGen, link), link);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -249,7 +235,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
}
|
||||
else
|
||||
{
|
||||
_items.TryAdd(key, new LinkedNode<TValue>(value, _liveGen));
|
||||
_items.TryAdd(key, new LinkedNode(value, _liveGen));
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -275,7 +261,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
if (kvp.Value.Gen < _liveGen)
|
||||
{
|
||||
var link = new LinkedNode<TValue>(null, _liveGen, kvp.Value);
|
||||
var link = new LinkedNode(null, _liveGen, kvp.Value);
|
||||
_items.TryUpdate(kvp.Key, link, kvp.Value);
|
||||
}
|
||||
else
|
||||
@@ -351,12 +337,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
Lock(lockInfo);
|
||||
|
||||
// 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());
|
||||
// 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());
|
||||
|
||||
// else we need to try to create a new gen object
|
||||
// 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 --
|
||||
@@ -364,32 +350,29 @@ 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 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)
|
||||
// create a new gen ref unless we already have it
|
||||
if (_generationObject == null)
|
||||
_generationObjects.Enqueue(_generationObject = new GenerationObject(snapGen));
|
||||
else if (_generationObject.Gen != snapGen)
|
||||
throw new Exception("panic");
|
||||
}
|
||||
else
|
||||
{
|
||||
// not write-locked, can use latest gen (_liveGen), create a corresponding new gen object
|
||||
_genObjs.Enqueue(_genObj = new GenObj(_liveGen));
|
||||
// not write-locked, can use latest gen, create a new gen ref
|
||||
_generationObjects.Enqueue(_generationObject = new GenerationObject(_liveGen));
|
||||
_nextGen = false; // this is the ONLY thing that triggers a _liveGen++
|
||||
}
|
||||
|
||||
// so...
|
||||
// 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
|
||||
// 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
|
||||
// so after a while, one of these conditions is going to be true:
|
||||
// - genObj.Count is zero because all snapshots have properly been disposed
|
||||
// - genObj.WeakGenRef is dead because all snapshots have been collected
|
||||
// - the genRefRef counter is zero because all snapshots have properly been disposed
|
||||
// - 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, _genObj.GetGenRef());
|
||||
var snapshot = new Snapshot(this, _generationObject.GetReference());
|
||||
|
||||
// reading _floorGen is safe if _collectTask is null
|
||||
if (_collectTask == null && _collectAuto && _liveGen - _floorGen > CollectMinGenDelta)
|
||||
@@ -433,16 +416,16 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
private void Collect()
|
||||
{
|
||||
// see notes in CreateSnapshot
|
||||
while (_genObjs.TryPeek(out var genObj) && (genObj.Count == 0 || genObj.WeakGenRef.IsAlive == false))
|
||||
while (_generationObjects.TryPeek(out GenerationObject generationObject) && (generationObject.Count == 0 || generationObject.WeakReference.IsAlive == false))
|
||||
{
|
||||
_genObjs.TryDequeue(out genObj); // cannot fail since TryPeek has succeeded
|
||||
_floorGen = genObj.Gen;
|
||||
_generationObjects.TryDequeue(out generationObject); // cannot fail since TryPeek has succeeded
|
||||
_floorGen = generationObject.Gen;
|
||||
}
|
||||
|
||||
Collect(_items);
|
||||
}
|
||||
|
||||
private void Collect(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict)
|
||||
private void Collect(ConcurrentDictionary<TKey, LinkedNode> 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
|
||||
@@ -477,7 +460,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<TValue>>>;
|
||||
var idict = dict as ICollection<KeyValuePair<TKey, LinkedNode>>;
|
||||
/*var removed =*/ idict.Remove(kvp);
|
||||
//Console.WriteLine("remove (" + (removed ? "true" : "false") + ")");
|
||||
continue;
|
||||
@@ -502,14 +485,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
task = _collectTask;
|
||||
}
|
||||
return task ?? Task.CompletedTask;
|
||||
return task ?? Task.FromResult(0);
|
||||
//if (task != null)
|
||||
// await task;
|
||||
}
|
||||
|
||||
public long GenCount => _genObjs.Count;
|
||||
public long GenCount => _generationObjects.Count;
|
||||
|
||||
public long SnapCount => _genObjs.Sum(x => x.Count);
|
||||
public long SnapCount => _generationObjects.Sum(x => x.Count);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -530,7 +513,6 @@ 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
|
||||
{
|
||||
@@ -538,15 +520,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
set => _dict._collectAuto = value;
|
||||
}
|
||||
|
||||
public GenObj GenObj => _dict._genObj;
|
||||
|
||||
public ConcurrentQueue<GenObj> GenObjs => _dict._genObjs;
|
||||
public ConcurrentQueue<GenerationObject> GenerationObjects => _dict._generationObjects;
|
||||
|
||||
public Snapshot LiveSnapshot => new Snapshot(_dict, _dict._liveGen);
|
||||
|
||||
public GenVal[] GetValues(TKey key)
|
||||
{
|
||||
_dict._items.TryGetValue(key, out var link); // else null
|
||||
_dict._items.TryGetValue(key, out LinkedNode link); // else null
|
||||
|
||||
if (link == null)
|
||||
return new GenVal[0];
|
||||
@@ -579,23 +559,35 @@ 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 GenRef _genRef;
|
||||
private readonly long _gen; // copied for perfs
|
||||
private int _disposed;
|
||||
private readonly GenerationReference _generationReference;
|
||||
private long _gen; // copied for perfs
|
||||
|
||||
//private static int _count;
|
||||
//private readonly int _thisCount;
|
||||
|
||||
internal Snapshot(SnapDictionary<TKey, TValue> store, GenRef genRef)
|
||||
internal Snapshot(SnapDictionary<TKey, TValue> store, GenerationReference generationReference)
|
||||
{
|
||||
_store = store;
|
||||
_genRef = genRef;
|
||||
_gen = genRef.GenObj.Gen;
|
||||
_genRef.GenObj.Reference();
|
||||
//_thisCount = _count++;
|
||||
_generationReference = generationReference;
|
||||
_gen = generationReference.GenerationObject.Gen;
|
||||
_generationReference.GenerationObject.Reference();
|
||||
}
|
||||
|
||||
internal Snapshot(SnapDictionary<TKey, TValue> store, long gen)
|
||||
@@ -604,21 +596,17 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
_gen = gen;
|
||||
}
|
||||
|
||||
private void EnsureNotDisposed()
|
||||
{
|
||||
if (_disposed > 0)
|
||||
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
||||
}
|
||||
|
||||
public TValue Get(TKey key)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
if (_gen < 0)
|
||||
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
||||
return _store.Get(key, _gen);
|
||||
}
|
||||
|
||||
public IEnumerable<TValue> GetAll()
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
if (_gen < 0)
|
||||
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
||||
return _store.GetAll(_gen);
|
||||
}
|
||||
|
||||
@@ -626,7 +614,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
if (_gen < 0)
|
||||
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
||||
return _store.IsEmpty(_gen);
|
||||
}
|
||||
}
|
||||
@@ -635,20 +624,63 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
if (_gen < 0)
|
||||
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
|
||||
return _gen;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
|
||||
return;
|
||||
_genRef?.GenObj.Release();
|
||||
if (_gen < 0) return;
|
||||
_gen = -1;
|
||||
_generationReference?.GenerationObject.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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user