-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathHybridCacheWrapper.cs
More file actions
129 lines (111 loc) · 4.74 KB
/
Copy pathHybridCacheWrapper.cs
File metadata and controls
129 lines (111 loc) · 4.74 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
129
using Microsoft.Extensions.Caching.Hybrid;
using NpgsqlRest;
namespace NpgsqlRestClient;
public class HybridCacheWrapper : IRoutineCache
{
private readonly HybridCache _cache;
private readonly ILogger? _logger;
public HybridCacheWrapper(HybridCache cache, ILogger? logger = null)
{
_cache = cache;
_logger = logger;
_logger?.LogInformation("HybridCache wrapper initialized");
}
// HybridCache rejects keys containing control bytes (notably \x00, which NpgsqlRest's null marker
// historically used, and which is unsafe across Redis keys / log collectors) with
// "Cache key contains invalid content." We always hash into a 64-char hex string so the wrapper is a
// correct adapter regardless of source-key content. (UseHashedCacheKeys / HashKeyThreshold remain a
// Redis-memory optimization for the Redis backend; they are a no-op here since this always hashes.)
private static string GetSafeKey(string key)
{
return CacheKeyHasher.ComputeHash(key);
}
public bool Get(RoutineEndpoint endpoint, string key, out object? result)
{
result = null;
try
{
var effectiveKey = GetSafeKey(key);
// HybridCache uses async API, we need to block here since IRoutineCache is synchronous
var task = _cache.GetOrCreateAsync<string?>(
effectiveKey,
cancel => new ValueTask<string?>((string?)null),
new HybridCacheEntryOptions
{
Flags = HybridCacheEntryFlags.DisableUnderlyingData
});
var cachedValue = task.AsTask().GetAwaiter().GetResult();
if (cachedValue is not null)
{
result = cachedValue;
_logger?.LogTrace("Cache hit for key: {Key}", key);
return true;
}
_logger?.LogTrace("Cache miss for key: {Key}", key);
return false;
}
catch (Exception ex)
{
_logger?.LogError(ex, "Error while getting key from HybridCache: {Key}", key);
return false;
}
}
public void AddOrUpdate(RoutineEndpoint endpoint, string key, object? value, TimeSpan? overrideExpiration = null)
{
try
{
var effectiveKey = GetSafeKey(key);
var stringValue = value?.ToString();
var expiry = overrideExpiration ?? endpoint.CacheExpiresIn;
var entryOptions = expiry.HasValue
? new HybridCacheEntryOptions { Expiration = expiry.Value, LocalCacheExpiration = expiry.Value }
: new HybridCacheEntryOptions();
// HybridCache.SetAsync is used to store values directly
var task = _cache.SetAsync(effectiveKey, stringValue, entryOptions);
task.AsTask().GetAwaiter().GetResult();
_logger?.LogTrace("Cached value for key: {Key} with expiry: {Expiry}", key, expiry);
}
catch (Exception ex)
{
_logger?.LogError(ex, "Error while setting key in HybridCache: {Key}", key);
}
}
public async ValueTask<object?> GetOrCreateAsync(
RoutineEndpoint endpoint,
string key,
Func<CancellationToken, ValueTask<object?>> factory,
TimeSpan? overrideExpiration = null,
CancellationToken cancellationToken = default)
{
// Delegate straight to HybridCache.GetOrCreateAsync: N concurrent calls with the same key
// share ONE factory invocation (its built-in stampede protection). Values are stored as the
// serialized string form, matching AddOrUpdate above (binary payloads are not supported by the
// Hybrid backend, same as before). The factory's exceptions propagate without being cached.
var effectiveKey = GetSafeKey(key);
var expiry = overrideExpiration ?? endpoint.CacheExpiresIn;
var options = expiry.HasValue
? new HybridCacheEntryOptions { Expiration = expiry.Value, LocalCacheExpiration = expiry.Value }
: new HybridCacheEntryOptions();
return await _cache.GetOrCreateAsync<string?>(
effectiveKey,
async ct => (await factory(ct))?.ToString(),
options,
cancellationToken: cancellationToken);
}
public bool Remove(string key)
{
try
{
var effectiveKey = GetSafeKey(key);
var task = _cache.RemoveAsync(effectiveKey);
task.AsTask().GetAwaiter().GetResult();
_logger?.LogTrace("Removed cache key: {Key}", key);
return true;
}
catch (Exception ex)
{
_logger?.LogError(ex, "Error while removing key from HybridCache: {Key}", key);
return false;
}
}
}