• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Aldaviva / Unfucked / 19128584806

06 Nov 2025 07:47AM UTC coverage: 0.396% (-39.5%) from 39.923%
19128584806

push

github

Aldaviva
DateTime: added IsBefore and IsAfter for ZonedDateTime, not just OffsetDateTime. DI: Allow super registration for keyed services; fixed super registration of a hosted service causing an infinite recursion during injection. STUN: updated fallback server list, most of which have gone offline (including Google, confusingly); made HttpClient optional.

2 of 1605 branches covered (0.12%)

0 of 55 new or added lines in 2 files covered. (0.0%)

945 existing lines in 35 files now uncovered.

9 of 2272 relevant lines covered (0.4%)

0.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/Caching/InMemoryCache.cs
1
using System.Collections.Concurrent;
2
using System.Timers;
3
using Timer = System.Timers.Timer;
4

5
namespace Unfucked.Caching;
6

7
public class InMemoryCache<K, V>: Cache<K, V> where K: notnull {
8

9
    public event RemovalNotification<K, V>? Removal;
10

11
    private readonly CacheOptions                           options;
12
    private readonly Func<K, ValueTask<V>>?                 defaultLoader;
13
    private readonly ConcurrentDictionary<K, CacheEntry<V>> cache;
14
    private readonly Timer?                                 expirationTimer;
15

16
    private volatile bool isDisposed;
17

UNCOV
18
    public InMemoryCache(CacheOptions? options = null, Func<K, ValueTask<V>>? loader = null) {
×
UNCOV
19
        this.options = (options ?? new CacheOptions()) with {
×
UNCOV
20
            ConcurrencyLevel = this.options.ConcurrencyLevel is > 0 and var c ? c : Environment.ProcessorCount,
×
UNCOV
21
            InitialCapacity = this.options.InitialCapacity is > 0 and var i ? i : 31
×
UNCOV
22
        };
×
23

UNCOV
24
        defaultLoader = loader;
×
UNCOV
25
        cache         = new ConcurrentDictionary<K, CacheEntry<V>>(this.options.ConcurrencyLevel, this.options.InitialCapacity);
×
26

UNCOV
27
        if (this.options.ExpireAfterWrite > TimeSpan.Zero || this.options.ExpireAfterRead > TimeSpan.Zero) {
×
UNCOV
28
            TimeSpan expirationScanInterval = this.options.ExpirationScanInterval is { Ticks: > 0 } interval ? interval : TimeSpan.FromMinutes(1);
×
UNCOV
29
            expirationTimer         =  new Timer(expirationScanInterval.TotalMilliseconds) { AutoReset = false };
×
UNCOV
30
            expirationTimer.Elapsed += ScanForExpirations;
×
UNCOV
31
            expirationTimer.Start();
×
32
        }
UNCOV
33
    }
×
34

35
    /// <inheritdoc />
UNCOV
36
    public long Count => cache.Count;
×
37

38
    /// <inheritdoc />
39
    public async Task<V> Get(K key, Func<K, ValueTask<V>>? loader = null) {
UNCOV
40
        CacheEntry<V> cacheEntry = cache.GetOrAdd(key, ValueFactory);
×
UNCOV
41
        if (cacheEntry.IsNew) {
×
UNCOV
42
            await cacheEntry.ValueLock.WaitAsync().ConfigureAwait(false);
×
43
            try {
UNCOV
44
                if (cacheEntry.IsNew) {
×
UNCOV
45
                    cacheEntry.IsNew = false;
×
UNCOV
46
                    cacheEntry.Value = await LoadValue(key, loader ?? defaultLoader).ConfigureAwait(false);
×
UNCOV
47
                    cacheEntry.LastWritten.Start();
×
UNCOV
48
                    cacheEntry.RefreshTimer?.Start();
×
49
                }
UNCOV
50
            } catch (KeyNotFoundException) {
×
UNCOV
51
                cache.TryRemove(key, out _);
×
UNCOV
52
                cacheEntry.Dispose();
×
UNCOV
53
                throw;
×
54
            } finally {
UNCOV
55
                if (!cacheEntry.IsDisposed) {
×
UNCOV
56
                    cacheEntry.ValueLock.Release();
×
57
                }
58
            }
UNCOV
59
        } else if (IsExpired(cacheEntry)) {
×
UNCOV
60
            V? oldValue = default;
×
UNCOV
61
            await cacheEntry.ValueLock.WaitAsync().ConfigureAwait(false);
×
62
            try {
UNCOV
63
                if (IsExpired(cacheEntry)) {
×
UNCOV
64
                    cacheEntry.RefreshTimer?.Stop();
×
65
                    try {
UNCOV
66
                        oldValue         = cacheEntry.Value;
×
UNCOV
67
                        cacheEntry.Value = await LoadValue(key, loader ?? defaultLoader).ConfigureAwait(false);
×
UNCOV
68
                        cacheEntry.LastWritten.Restart();
×
UNCOV
69
                    } finally {
×
UNCOV
70
                        cacheEntry.RefreshTimer?.Start();
×
71
                    }
72
                }
UNCOV
73
            } finally {
×
UNCOV
74
                cacheEntry.ValueLock.Release();
×
75
            }
76

UNCOV
77
            if (oldValue is not null) {
×
UNCOV
78
                Removal?.Invoke(this, key, oldValue, RemovalCause.EXPIRED);
×
79
            }
UNCOV
80
        }
×
81

UNCOV
82
        cacheEntry.LastRead.Restart();
×
UNCOV
83
        return cacheEntry.Value;
×
UNCOV
84
    }
×
85

86
    /// <inheritdoc />
87
    public async Task Put(K key, V value) {
UNCOV
88
        CacheEntry<V> cacheEntry = cache.GetOrAdd(key, ValueFactory);
×
UNCOV
89
        await cacheEntry.ValueLock.WaitAsync().ConfigureAwait(false);
×
UNCOV
90
        V? removedValue = default;
×
91
        try {
UNCOV
92
            cacheEntry.RefreshTimer?.Stop();
×
UNCOV
93
            if (cacheEntry.IsNew) {
×
UNCOV
94
                cacheEntry.IsNew = false;
×
95
            } else {
UNCOV
96
                removedValue = cacheEntry.Value;
×
97
            }
98

UNCOV
99
            cacheEntry.Value = value;
×
UNCOV
100
            cacheEntry.LastWritten.Restart();
×
UNCOV
101
            cacheEntry.RefreshTimer?.Start();
×
UNCOV
102
        } finally {
×
UNCOV
103
            cacheEntry.ValueLock.Release();
×
104
        }
105

UNCOV
106
        if (removedValue is not null) {
×
UNCOV
107
            Removal?.Invoke(this, key, removedValue, RemovalCause.REPLACED);
×
108
        }
UNCOV
109
    }
×
110

111
    private CacheEntry<V> ValueFactory(K key) {
UNCOV
112
        bool   hasLoader    = defaultLoader != null;
×
UNCOV
113
        Timer? refreshTimer = options.RefreshAfterWrite > TimeSpan.Zero && hasLoader ? new Timer(options.RefreshAfterWrite.TotalMilliseconds) { AutoReset = false, Enabled = false } : null;
×
UNCOV
114
        var    entry        = new CacheEntry<V>(refreshTimer);
×
115

UNCOV
116
        if (entry.RefreshTimer != null) {
×
117
            async void refreshEntry(object o, ElapsedEventArgs elapsedEventArgs) {
UNCOV
118
                if (!entry.IsDisposed) {
×
119
                    V oldValue;
UNCOV
120
                    await entry.ValueLock.WaitAsync().ConfigureAwait(false);
×
121
                    try {
UNCOV
122
                        oldValue    = entry.Value;
×
UNCOV
123
                        entry.Value = await defaultLoader!(key).ConfigureAwait(false);
×
UNCOV
124
                        entry.LastWritten.Restart();
×
UNCOV
125
                        entry.RefreshTimer.Start();
×
UNCOV
126
                    } finally {
×
UNCOV
127
                        entry.ValueLock.Release();
×
128
                    }
UNCOV
129
                    Removal?.Invoke(this, key, oldValue, RemovalCause.REPLACED);
×
UNCOV
130
                } else {
×
131
                    entry.RefreshTimer.Elapsed -= refreshEntry;
×
132
                }
UNCOV
133
            }
×
134

UNCOV
135
            entry.RefreshTimer.Elapsed += refreshEntry;
×
136
        }
137

UNCOV
138
        return entry;
×
139
    }
140

141
    /// <exception cref="System.Collections.Generic.KeyNotFoundException">a value with the key <typeparamref name="K"/> was not found, and no <paramref name="loader"/> was not provided</exception>
142
    private static ValueTask<V> LoadValue(K key, Func<K, ValueTask<V>>? loader) {
UNCOV
143
        if (loader != null) {
×
UNCOV
144
            return loader(key);
×
145
        } else {
UNCOV
146
            throw KeyNotFoundException(key);
×
147
        }
148
    }
149

UNCOV
150
    private static KeyNotFoundException KeyNotFoundException(K key) => new(
×
UNCOV
151
        $"Value with key {key} not found in cache, and a loader function was not provided when constructing the {nameof(InMemoryCache<K, V>)} or getting the value.");
×
152

153
    private bool IsExpired(CacheEntry<V> cacheEntry) =>
UNCOV
154
        (options.ExpireAfterWrite > TimeSpan.Zero && options.ExpireAfterWrite <= cacheEntry.LastWritten.Elapsed)
×
UNCOV
155
        || (options.ExpireAfterRead > TimeSpan.Zero && options.ExpireAfterRead <= cacheEntry.LastRead.Elapsed);
×
156

157
    /// <inheritdoc />
158
    public void CleanUp() {
UNCOV
159
        foreach (KeyValuePair<K, CacheEntry<V>> entry in cache.Where(pair => IsExpired(pair.Value))) {
×
UNCOV
160
            entry.Value.ValueLock.Wait();
×
UNCOV
161
            bool removed = false;
×
UNCOV
162
            if (IsExpired(entry.Value)) {
×
163
                //this will probably throw a concurrent modification exception
UNCOV
164
                removed = cache.TryRemove(entry.Key, out _);
×
UNCOV
165
                if (removed) {
×
UNCOV
166
                    entry.Value.Dispose();
×
UNCOV
167
                    Removal?.Invoke(this, entry.Key, entry.Value.Value, RemovalCause.EXPIRED);
×
168
                }
169
            }
170

UNCOV
171
            if (!removed) {
×
172
                /*
173
                 * First pass showed entry as expired, but it was concurrently loaded or refreshed before this second pass check, so don't actually remove it.
174
                 * Only release if we didn't remove, because if we removed the entry and its lock were already disposed.
175
                 */
176
                entry.Value.ValueLock.Release();
×
177
            }
178
        }
UNCOV
179
    }
×
180

181
    private void ScanForExpirations(object? sender = null, ElapsedEventArgs? e = null) {
UNCOV
182
        CleanUp();
×
UNCOV
183
        expirationTimer!.Start();
×
UNCOV
184
    }
×
185

186
    /// <inheritdoc />
187
    public void Invalidate(params IEnumerable<K> keys) {
UNCOV
188
        foreach (K key in keys) {
×
UNCOV
189
            if (cache.TryRemove(key, out CacheEntry<V>? removedEntry)) {
×
UNCOV
190
                Removal?.Invoke(this, key, removedEntry.Value, RemovalCause.EXPLICIT);
×
UNCOV
191
                removedEntry.Dispose();
×
192
            }
193
        }
UNCOV
194
    }
×
195

196
    /// <inheritdoc />
197
    public void InvalidateAll() {
UNCOV
198
        KeyValuePair<K, CacheEntry<V>>[] toDispose = cache.ToArray();
×
UNCOV
199
        cache.Clear();
×
UNCOV
200
        foreach (KeyValuePair<K, CacheEntry<V>> entry in toDispose) {
×
UNCOV
201
            if (!isDisposed) {
×
UNCOV
202
                Removal?.Invoke(this, entry.Key, entry.Value.Value, RemovalCause.EXPLICIT);
×
203
            }
UNCOV
204
            entry.Value.Dispose();
×
205
        }
UNCOV
206
    }
×
207

208
    protected virtual void Dispose(bool disposing) {
UNCOV
209
        if (disposing) {
×
UNCOV
210
            isDisposed = true;
×
UNCOV
211
            if (expirationTimer != null) {
×
UNCOV
212
                expirationTimer.Elapsed -= ScanForExpirations;
×
UNCOV
213
                expirationTimer.Dispose();
×
214
            }
UNCOV
215
            InvalidateAll();
×
216
        }
UNCOV
217
    }
×
218

219
    /// <inheritdoc />
220
    public void Dispose() {
UNCOV
221
        Dispose(true);
×
UNCOV
222
        GC.SuppressFinalize(this);
×
UNCOV
223
    }
×
224

225
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc