Implemented query cache size limit in front-end and in logic, Reworked LRUCache for performance, Fixed updating entities from front-end not working
This commit is contained in:
@@ -1,215 +1,240 @@
|
||||
namespace Shared;
|
||||
|
||||
|
||||
public class EnumerableLruCache<TKey, TValue> where TKey : notnull
|
||||
public sealed class EnumerableLruCache<TKey, TValue> where TKey : notnull
|
||||
{
|
||||
private readonly Dictionary<TKey, TValue> _cache;
|
||||
private readonly LinkedList<TKey> _keys = new();
|
||||
private sealed record CacheItem(TKey Key, TValue Value);
|
||||
|
||||
private readonly Dictionary<TKey, LinkedListNode<CacheItem>> _map;
|
||||
private readonly LinkedList<CacheItem> _lruList;
|
||||
private readonly ReaderWriterLockSlim _lock = new();
|
||||
|
||||
private int _capacity;
|
||||
private ReaderWriterLockSlim _readerWriterLock;
|
||||
|
||||
public EnumerableLruCache(int capacity)
|
||||
{
|
||||
if (capacity <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
|
||||
_capacity = capacity;
|
||||
_map = new Dictionary<TKey, LinkedListNode<CacheItem>>(capacity);
|
||||
_lruList = new LinkedList<CacheItem>();
|
||||
}
|
||||
|
||||
public int Capacity
|
||||
{
|
||||
get
|
||||
{
|
||||
_readerWriterLock.EnterReadLock();
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _capacity;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readerWriterLock.ExitReadLock();
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
_readerWriterLock.EnterWriteLock();
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
|
||||
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_capacity = value;
|
||||
|
||||
// Trim cache if new capacity is smaller than current size
|
||||
while (_keys.Count > _capacity)
|
||||
{
|
||||
TKey last = _keys.Last!.Value;
|
||||
_keys.RemoveLast();
|
||||
_cache.Remove(last);
|
||||
}
|
||||
TrimIfNeeded();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readerWriterLock.ExitWriteLock();
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EnumerableLruCache(int capacity)
|
||||
public int Count
|
||||
{
|
||||
_readerWriterLock = new();
|
||||
_capacity = capacity;
|
||||
_cache = [];
|
||||
get
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _map.Count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
_readerWriterLock.EnterReadLock();
|
||||
if (!TryGetValue(key, out var value))
|
||||
throw new KeyNotFoundException();
|
||||
|
||||
return value!;
|
||||
}
|
||||
set => Set(key, value);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue? value)
|
||||
{
|
||||
_lock.EnterUpgradeableReadLock();
|
||||
try
|
||||
{
|
||||
if (!_map.TryGetValue(key, out var node))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = node.Value.Value;
|
||||
|
||||
// LRU aktualisieren
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
return _cache[key];
|
||||
_lruList.Remove(node);
|
||||
_lruList.AddFirst(node);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readerWriterLock.ExitReadLock();
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
_readerWriterLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_cache.TryGetValue(key, out _))
|
||||
{
|
||||
_keys.Remove(key);
|
||||
}
|
||||
_keys.AddFirst(key);
|
||||
_cache[key] = value;
|
||||
|
||||
if (_keys.Count > _capacity)
|
||||
{
|
||||
TKey last = _keys.Last!.Value;
|
||||
_keys.RemoveLast();
|
||||
_cache.Remove(last);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readerWriterLock.ExitWriteLock();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
_readerWriterLock.EnterReadLock();
|
||||
try
|
||||
finally
|
||||
{
|
||||
return _cache.ContainsKey(key);
|
||||
} finally
|
||||
{
|
||||
_readerWriterLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public int Count()
|
||||
{
|
||||
_readerWriterLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _cache.Count;
|
||||
} finally
|
||||
{
|
||||
_readerWriterLock.ExitReadLock();
|
||||
_lock.ExitUpgradeableReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(TKey key, TValue value)
|
||||
{
|
||||
_readerWriterLock.EnterWriteLock();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_cache.TryGetValue(key, out _))
|
||||
if (_map.TryGetValue(key, out var existing))
|
||||
{
|
||||
_keys.Remove(key);
|
||||
// Update + nach vorne
|
||||
existing.Value = existing.Value with { Value = value };
|
||||
_lruList.Remove(existing);
|
||||
_lruList.AddFirst(existing);
|
||||
return;
|
||||
}
|
||||
_keys.AddFirst(key);
|
||||
_cache[key] = value;
|
||||
|
||||
if (_keys.Count > _capacity)
|
||||
{
|
||||
TKey? last = _keys.Last();
|
||||
_keys.RemoveLast();
|
||||
_cache.Remove(last);
|
||||
}
|
||||
} finally
|
||||
var item = new CacheItem(key, value);
|
||||
var node = new LinkedListNode<CacheItem>(item);
|
||||
|
||||
_lruList.AddFirst(node);
|
||||
_map[key] = node;
|
||||
|
||||
TrimIfNeeded();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readerWriterLock.ExitWriteLock();
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(TKey key)
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
_readerWriterLock.EnterWriteLock();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_keys.Remove(key);
|
||||
_cache.Remove(key);
|
||||
} finally
|
||||
{
|
||||
_readerWriterLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue? value)
|
||||
{
|
||||
_readerWriterLock.EnterUpgradeableReadLock();
|
||||
try
|
||||
{
|
||||
if (_cache.TryGetValue(key, out value))
|
||||
{
|
||||
if (!_map.TryGetValue(key, out var node))
|
||||
return false;
|
||||
}
|
||||
_readerWriterLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_keys.Remove(key);
|
||||
_keys.AddFirst(key);
|
||||
} finally
|
||||
{
|
||||
_readerWriterLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
_lruList.Remove(node);
|
||||
_map.Remove(key);
|
||||
return true;
|
||||
} finally
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readerWriterLock.ExitUpgradeableReadLock();
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _map.ContainsKey(key);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue> AsDictionary()
|
||||
{
|
||||
_readerWriterLock.EnterReadLock();
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return new Dictionary<TKey, TValue>(_cache);
|
||||
return _map.Values.ToDictionary(
|
||||
n => n.Value.Key,
|
||||
n => n.Value.Value
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readerWriterLock.ExitReadLock();
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<TKey, TValue>> Items()
|
||||
{
|
||||
_readerWriterLock.EnterReadLock();
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
foreach (var key in _keys)
|
||||
foreach (var item in _lruList)
|
||||
{
|
||||
if (_cache.TryGetValue(key, out var value))
|
||||
{
|
||||
yield return new KeyValuePair<TKey, TValue>(key, value);
|
||||
}
|
||||
yield return new KeyValuePair<TKey, TValue>(item.Key, item.Value);
|
||||
}
|
||||
} finally
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readerWriterLock.ExitReadLock();
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return Items().GetEnumerator();
|
||||
List<KeyValuePair<TKey, TValue>> snapshot;
|
||||
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
snapshot = new List<KeyValuePair<TKey, TValue>>(_map.Count);
|
||||
|
||||
foreach (var item in _lruList)
|
||||
{
|
||||
snapshot.Add(new KeyValuePair<TKey, TValue>(
|
||||
item.Key,
|
||||
item.Value
|
||||
));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
|
||||
return snapshot.GetEnumerator();
|
||||
}
|
||||
|
||||
private void TrimIfNeeded()
|
||||
{
|
||||
while (_map.Count > _capacity)
|
||||
{
|
||||
var lruNode = _lruList.Last!;
|
||||
_lruList.RemoveLast();
|
||||
_map.Remove(lruNode.Value.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user