-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathRoutineCache.cs
More file actions
128 lines (110 loc) · 3.96 KB
/
Copy pathRoutineCache.cs
File metadata and controls
128 lines (110 loc) · 3.96 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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
namespace NpgsqlRest;
public interface IRoutineCache
{
bool Get(RoutineEndpoint endpoint, string key, out object? result);
/// <summary>
/// Stores or updates a cached value.
/// <paramref name="overrideExpiration"/> (when non-null) takes precedence over <see cref="RoutineEndpoint.CacheExpiresIn"/>
/// and is used by <see cref="CacheWhenRule"/> "Then" overrides to apply a per-request TTL.
/// </summary>
void AddOrUpdate(RoutineEndpoint endpoint, string key, object? value, TimeSpan? overrideExpiration = null);
bool Remove(string key);
}
public static class CacheKeyHasher
{
/// <summary>
/// Computes a SHA256 hash of the cache key and returns it as a hex string.
/// The result is always 64 characters (256 bits / 4 bits per hex char).
/// </summary>
public static string ComputeHash(string key)
{
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(key));
return Convert.ToHexString(hash);
}
/// <summary>
/// Returns the effective cache key based on the hashing configuration.
/// If UseHashedCacheKeys is true and the key length exceeds HashKeyThreshold, returns a hashed key.
/// Otherwise, returns the original key.
/// </summary>
public static string GetEffectiveKey(string key, CacheOptions options)
{
if (options.UseHashedCacheKeys && key.Length > options.HashKeyThreshold)
{
return ComputeHash(key);
}
return key;
}
}
public class RoutineCache : IRoutineCache
{
private class CacheEntry
{
public object? Value { get; set; }
public DateTime? ExpirationTime { get; set; }
public bool IsExpired => ExpirationTime.HasValue && DateTime.UtcNow > ExpirationTime.Value;
}
private static readonly ConcurrentDictionary<string, CacheEntry> _cache = new(StringComparer.Ordinal);
private static Timer? _cleanupTimer;
private static CacheOptions _options = new();
public static void Start(NpgsqlRestOptions options)
{
_options = options.CacheOptions;
_cleanupTimer = new Timer(
_ => CleanupExpiredEntriesInternal(),
null,
TimeSpan.FromSeconds(options.CacheOptions.MemoryCachePruneIntervalSeconds),
TimeSpan.FromSeconds(options.CacheOptions.MemoryCachePruneIntervalSeconds));
}
public static void Shutdown()
{
_cleanupTimer?.Dispose();
_cache.Clear();
}
private static void CleanupExpiredEntriesInternal()
{
var expiredKeys = _cache
.Where(kvp => kvp.Value.IsExpired)
.Select(kvp => kvp.Key)
.ToList();
foreach (var key in expiredKeys)
{
_cache.TryRemove(key, out _);
}
}
public bool Get(RoutineEndpoint endpoint, string key, out object? result)
{
var effectiveKey = CacheKeyHasher.GetEffectiveKey(key, _options);
if (_cache.TryGetValue(effectiveKey, out var entry))
{
if (entry.IsExpired)
{
_cache.TryRemove(effectiveKey, out _);
result = null;
return false;
}
result = entry.Value;
return true;
}
result = null;
return false;
}
public void AddOrUpdate(RoutineEndpoint endpoint, string key, object? value, TimeSpan? overrideExpiration = null)
{
var effectiveKey = CacheKeyHasher.GetEffectiveKey(key, _options);
var ttl = overrideExpiration ?? endpoint.CacheExpiresIn;
var entry = new CacheEntry
{
Value = value,
ExpirationTime = ttl.HasValue ? DateTime.UtcNow + ttl.Value : null
};
_cache[effectiveKey] = entry;
}
public bool Remove(string key)
{
var effectiveKey = CacheKeyHasher.GetEffectiveKey(key, _options);
return _cache.TryRemove(effectiveKey, out _);
}
}