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:
2026-01-05 01:04:26 +01:00
parent 88d1b27394
commit 3dfcaa19e6
4 changed files with 185 additions and 130 deletions

View File

@@ -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);
}
}
}