Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f53c2f0ed | ||
|
|
7562ded92a | ||
|
|
392b9ec212 | ||
|
|
769c7819e0 | ||
|
|
e8eb90a085 |
@@ -1,4 +1,4 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using JoshHeaps.Net.Models;
|
using JoshHeaps.Net.Models;
|
||||||
using JoshHeaps.Net.Services.Interfaces;
|
using JoshHeaps.Net.Services.Interfaces;
|
||||||
@@ -10,6 +10,7 @@ public class BlogService : IBlogService
|
|||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly ILogger<BlogService> _logger;
|
private readonly ILogger<BlogService> _logger;
|
||||||
private readonly TimeSpan _cacheTtl = TimeSpan.FromMinutes(5);
|
private readonly TimeSpan _cacheTtl = TimeSpan.FromMinutes(5);
|
||||||
|
private readonly TimeSpan _staleGrace = TimeSpan.FromHours(1);
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, CacheEntry> _cache = new();
|
private readonly ConcurrentDictionary<string, CacheEntry> _cache = new();
|
||||||
|
|
||||||
@@ -65,7 +66,9 @@ public class BlogService : IBlogService
|
|||||||
|
|
||||||
private async Task<T?> GetCachedAsync<T>(string key, Func<Task<T?>> factory) where T : class
|
private async Task<T?> GetCachedAsync<T>(string key, Func<Task<T?>> 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;
|
return (T?)entry.Value;
|
||||||
|
|
||||||
var result = await factory();
|
var result = await factory();
|
||||||
@@ -73,15 +76,26 @@ public class BlogService : IBlogService
|
|||||||
if (result is not null)
|
if (result is not null)
|
||||||
{
|
{
|
||||||
_cache[key] = new CacheEntry(result, DateTime.UtcNow.Add(_cacheTtl));
|
_cache[key] = new CacheEntry(result, DateTime.UtcNow.Add(_cacheTtl));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else if (entry is not null)
|
|
||||||
|
return entry is null ? null : ServeStale<T>(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<T>(string key, CacheEntry entry) where T : class
|
||||||
|
{
|
||||||
|
if (entry.ExpiresAt.Add(_staleGrace) > DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
// API unreachable — serve stale cache
|
_logger.LogWarning("Blog API unreachable, serving stale cache for key {Key}", key);
|
||||||
_logger.LogWarning("Serving stale cache for key {Key}", key);
|
|
||||||
return (T?)entry.Value;
|
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()
|
public void ClearCache()
|
||||||
|
|||||||
@@ -100,11 +100,26 @@ public sealed class Stockfish : IChessEngine
|
|||||||
if (line == token) break;
|
if (line == token) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If Stockfish doesn't exit on "quit" (crashed, zombie, ignores stdin close), don't hang
|
||||||
|
// here forever — that would block GameStore's removal task and leave the finished game
|
||||||
|
// stuck in the store, cluttering the watch page indefinitely.
|
||||||
|
private static readonly TimeSpan _exitTimeout = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
Send("quit");
|
Send("quit");
|
||||||
_stdin.Close();
|
_stdin.Close();
|
||||||
await _p.WaitForExitAsync();
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _p.WaitForExitAsync().WaitAsync(_exitTimeout);
|
||||||
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
if (!_p.HasExited)
|
||||||
|
_p.Kill(entireProcessTree: true);
|
||||||
|
}
|
||||||
|
|
||||||
_p.Dispose();
|
_p.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user