Repository navigation
Expand file tree
/
Copy pathCacheService.cs
More file actions
131 lines (113 loc) · 4.06 KB
/
Copy pathCacheService.cs
File metadata and controls
131 lines (113 loc) · 4.06 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
130
131
/*
* Steam Api Client
*
* Copyright (c) 2026 Kyle Givler
* Licensed under the MIT License.
*/
using Microsoft.Extensions.Caching.Hybrid;
using Microsoft.Extensions.Logging;
using SteamApiClient.Extensions;
using SteamApiClient.Settings;
namespace SteamApiClient.Services;
internal class CacheService : ICacheService
{
private readonly HybridCache _cache;
private readonly ILogger<CacheService> _logger;
public CacheService(
HybridCache cache,
ILogger<CacheService> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T> CoalesceAsync<T>(string key, Func<CancellationToken, Task<T>> factory, CancellationToken ct = default)
{
// Retain HybridCache's stampede protection and combined cancellation, without storing
// a shared result that would blur success/not-found policies or cache transient failures.
var options = new HybridCacheEntryOptions
{
Flags = HybridCacheEntryFlags.DisableLocalCache | HybridCacheEntryFlags.DisableDistributedCache
};
return await _cache.GetOrCreateAsync<T>(key, token => new ValueTask<T>(factory(token)),
options, cancellationToken: ct);
}
public async Task<T> GetOrCreateAsync<T>(
string key,
Func<CancellationToken, Task<T>> factory,
CachePolicy policy,
IEnumerable<string>? tags = null,
CancellationToken ct = default)
{
var options = new HybridCacheEntryOptions
{
Expiration = policy.Duration,
LocalCacheExpiration = HybridCacheExtensions.LocalCacheExpiration
};
//_logger.LogDebug("HybridCache executing lookups for key: {Key}", key);
return await _cache.GetOrCreateAsync(
key,
async token => await factory(token),
options,
tags,
cancellationToken: ct);
}
public async Task SetAsync<T>(
string key,
T value,
CachePolicy policy,
IEnumerable<string>? tags = null,
CancellationToken ct = default)
{
if (value is null)
return;
var options = new HybridCacheEntryOptions
{
Expiration = policy.Duration,
LocalCacheExpiration = HybridCacheExtensions.LocalCacheExpiration
};
await _cache.SetAsync(key, value, options, tags, cancellationToken: ct);
// _logger.LogDebug("HybridCache direct write: {Key}", key);
}
public Task<T?> GetAsync<T>(string key, CancellationToken ct = default)
=> _cache.GetAsync<T>(key, ct);
public async Task<CacheLookupResult<T>> GetOrCreateWithMetadataAsync<T>(
string key,
Func<CancellationToken, Task<T>> factory,
CachePolicy policy,
IEnumerable<string>? tags = null,
CancellationToken ct = default)
{
var factoryExecuted = false;
var options = new HybridCacheEntryOptions
{
Expiration = policy.Duration,
LocalCacheExpiration = HybridCacheExtensions.LocalCacheExpiration
};
var cachedValue = await _cache.GetOrCreateAsync(
key,
async token =>
{
factoryExecuted = true;
var value = await factory(token);
return new CachedValue<T>(value, DateTimeOffset.UtcNow);
},
options,
tags,
cancellationToken: ct);
var ageSeconds = Math.Max(
0,
(long)(DateTimeOffset.UtcNow - cachedValue.CachedAt).TotalSeconds);
return new CacheLookupResult<T>(
cachedValue.Value,
new OwnedGamesCacheInfo(
factoryExecuted
? OwnedGamesCacheStatus.Miss
: OwnedGamesCacheStatus.Hit,
ageSeconds));
}
public async Task InvalidateByTagAsync(string tag, CancellationToken ct = default)
{
//_logger.LogDebug("Invalidating cache for tag: {Tag}", tag);
await _cache.RemoveByTagAsync(tag, cancellationToken: ct);
}
}