diff --git a/JoshHeaps.Net/Services/Implementations/BlogService.cs b/JoshHeaps.Net/Services/Implementations/BlogService.cs index a6ecad2..61e4e79 100644 --- a/JoshHeaps.Net/Services/Implementations/BlogService.cs +++ b/JoshHeaps.Net/Services/Implementations/BlogService.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Text.Json; using JoshHeaps.Net.Models; using JoshHeaps.Net.Services.Interfaces; @@ -10,6 +10,7 @@ public class BlogService : IBlogService private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly TimeSpan _cacheTtl = TimeSpan.FromMinutes(5); + private readonly TimeSpan _staleGrace = TimeSpan.FromHours(1); private readonly ConcurrentDictionary _cache = new(); @@ -65,7 +66,9 @@ public class BlogService : IBlogService private async Task GetCachedAsync(string key, Func> factory) where T : class { - if (_cache.TryGetValue(key, out var entry) && entry.ExpiresAt > DateTime.UtcNow) + _cache.TryGetValue(key, out var entry); + + if (entry is not null && entry.ExpiresAt > DateTime.UtcNow) return (T?)entry.Value; var result = await factory(); @@ -73,15 +76,26 @@ public class BlogService : IBlogService if (result is not null) { _cache[key] = new CacheEntry(result, DateTime.UtcNow.Add(_cacheTtl)); + return result; } - else if (entry is not null) + + return entry is null ? null : ServeStale(key, entry); + } + + // Stale entries are dropped once the grace window closes so that a dead API fails the same + // way for every key. Serving them indefinitely let a cached post list render next to 404s on + // the posts themselves, which reads as a site bug rather than an outage. + private T? ServeStale(string key, CacheEntry entry) where T : class + { + if (entry.ExpiresAt.Add(_staleGrace) > DateTime.UtcNow) { - // API unreachable — serve stale cache - _logger.LogWarning("Serving stale cache for key {Key}", key); + _logger.LogWarning("Blog API unreachable, serving stale cache for key {Key}", key); return (T?)entry.Value; } - return result; + _cache.TryRemove(key, out _); + _logger.LogError("Blog API unreachable for over {StaleGrace}, dropping stale cache for key {Key}", _staleGrace, key); + return null; } public void ClearCache()