-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathCacheKeyManager.cs
More file actions
101 lines (84 loc) · 3.14 KB
/
CacheKeyManager.cs
File metadata and controls
101 lines (84 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
namespace CleverCache;
using System.Collections.Concurrent;
internal abstract class CacheEntryManager
{
// type -> set of keys (thread-safe “set” via ConcurrentDictionary<TKey, byte>)
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, byte>> _keysByType = new();
// type -> dependents (direct edges). We’ll expand transitively at runtime.
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, byte>> _dependants = new();
/// <summary>
/// Adds a dependent relationship between two types.
/// </summary>
public void AddDependentCache(Type type, Type dependentType)
{
var set = _dependants.GetOrAdd(type, _ => new ConcurrentDictionary<Type, byte>());
set.TryAdd(dependentType, 0);
}
/// <summary>
/// Associates key with each type and all of its transitive dependents.
/// </summary>
public virtual void AddKeyToTypes(Type[] types, object key)
{
ArgumentNullException.ThrowIfNull(key);
if (!TryResolveCanonicalKey(key, out var canonicalKey))
throw new InvalidOperationException("The supplied key could not be converted into a stable cache key.");
AddCanonicalKeyToTypes(types, canonicalKey);
}
protected virtual bool TryResolveCanonicalKey(object key, out string canonicalKey) =>
CacheKeyIdentity.TryToCanonicalKey(key, out canonicalKey);
protected void AddCanonicalKeyToTypes(Type[] types, string canonicalKey)
{
var all = ExpandTransitively(types);
foreach (var t in all)
{
_keysByType.GetOrAdd(t, _ => new ConcurrentDictionary<string, byte>()).TryAdd(canonicalKey, 0);
}
}
/// <summary>
/// Removes a key from every type's tracked key set.
/// Called after a key is removed from the store so the tracking set stays in sync.
/// </summary>
protected void RemoveKeyFromAllTypes(object key)
{
var canonicalKey = key is string s && CacheKeyIdentity.IsCanonicalKey(s)
? s
: CacheKeyIdentity.ToCanonicalKey(key);
foreach (var typeSet in _keysByType.Values)
typeSet.TryRemove(canonicalKey, out _);
}
/// <summary>
/// Snapshot keys for a given type (safe to enumerate).
/// </summary>
protected string[] SnapshotKeysFor(Type type) =>
_keysByType.TryGetValue(type, out var set) ? set.Keys.ToArray() : [];
/// <summary>
/// Snapshot of the full dependency graph and tracked keys.
/// </summary>
protected CleverCacheDiagnostics SnapshotDiagnostics()
{
var dependants = _dependants.ToDictionary(
kvp => kvp.Key,
kvp => (IReadOnlyList<Type>)kvp.Value.Keys.OrderBy(t => t.Name).ToList());
var keysByType = _keysByType.ToDictionary(
kvp => kvp.Key,
kvp => (IReadOnlyList<object>)kvp.Value.Keys.Cast<object>().ToList());
return new CleverCacheDiagnostics(dependants, keysByType);
}
/// <summary>
/// Transitive closure over dependents, cycle-safe
/// </summary>
private HashSet<Type> ExpandTransitively(IEnumerable<Type> roots)
{
var visited = new HashSet<Type>();
var stack = new Stack<Type>(roots);
while (stack.Count > 0)
{
var t = stack.Pop();
if (!visited.Add(t)) continue;
if (!_dependants.TryGetValue(t, out var dependants)) continue;
foreach (var d in dependants.Keys)
if (!visited.Contains(d)) stack.Push(d);
}
return visited;
}
}