Compare commits
4
Commits
e8eb90a085
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7562ded92a | ||
|
|
392b9ec212 | ||
|
|
769c7819e0 | ||
|
|
c048529654 |
@@ -12,13 +12,17 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-deploy:
|
build-deploy:
|
||||||
|
# Gitea's runner images are lean, so the toolchain is installed below rather
|
||||||
|
# than assumed. The glibc that libchess_engine.so links against comes from
|
||||||
|
# this image, not from the runner host — keep it matched to the prod server.
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install deploy tools
|
- name: Install toolchain
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends rsync openssh-client
|
apt-get install -y --no-install-recommends \
|
||||||
|
cmake build-essential rsync openssh-client
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -29,10 +33,26 @@ jobs:
|
|||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
- name: Restore
|
- name: Restore
|
||||||
run: dotnet restore
|
run: dotnet restore JoshHeaps.Net/JoshHeaps.Net.csproj
|
||||||
|
|
||||||
|
- name: Build native chess engine (libchess_engine.so)
|
||||||
|
run: |
|
||||||
|
cmake -S native/chess_engine -B native/chess_engine/build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build native/chess_engine/build
|
||||||
|
cp native/chess_engine/build/libchess_engine.so JoshHeaps.Net/Resources/
|
||||||
|
|
||||||
|
# Publishing the project rather than the solution keeps chess_engine.vcxproj
|
||||||
|
# (Windows-only MSBuild C++ targets) and the UiTests assemblies out of it.
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: dotnet publish -c Release -o ./publish
|
run: dotnet publish JoshHeaps.Net/JoshHeaps.Net.csproj -c Release -o ./publish
|
||||||
|
|
||||||
|
- name: Verify payload
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
test -f publish/Resources/libchess_engine.so \
|
||||||
|
|| { echo "libchess_engine.so missing from publish output"; exit 1; }
|
||||||
|
test -x publish/Resources/stockfish-ubuntu-x86-64-sse41-popcnt \
|
||||||
|
|| { echo "stockfish is not executable"; exit 1; }
|
||||||
|
|
||||||
- name: Prepare SSH
|
- name: Prepare SSH
|
||||||
env:
|
env:
|
||||||
@@ -47,6 +67,8 @@ jobs:
|
|||||||
chmod 600 ~/.ssh/deploy_key
|
chmod 600 ~/.ssh/deploy_key
|
||||||
ssh-keyscan -p "$PORT" "$SSH_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
ssh-keyscan -p "$PORT" "$SSH_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
|
# --exclude chess-data: never let --delete remove the learned-engine training
|
||||||
|
# data, which lives in the deploy dir unless ChessEngine__WeightsPath is set.
|
||||||
- name: Rsync to server
|
- name: Rsync to server
|
||||||
env:
|
env:
|
||||||
SSH_HOST: ${{ secrets.SSH_HOST }}
|
SSH_HOST: ${{ secrets.SSH_HOST }}
|
||||||
@@ -56,11 +78,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
PORT="${SSH_PORT:-22}"
|
PORT="${SSH_PORT:-22}"
|
||||||
rsync -az --delete \
|
rsync -az --delete --exclude 'chess-data' \
|
||||||
-e "ssh -p $PORT -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes" \
|
-e "ssh -p $PORT -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes" \
|
||||||
publish/ "$SSH_USER@$SSH_HOST:$TARGET_DIR/"
|
publish/ "$SSH_USER@$SSH_HOST:$TARGET_DIR/"
|
||||||
|
|
||||||
- name: Restart service
|
- name: Reload and restart service
|
||||||
env:
|
env:
|
||||||
SSH_HOST: ${{ secrets.SSH_HOST }}
|
SSH_HOST: ${{ secrets.SSH_HOST }}
|
||||||
SSH_PORT: ${{ secrets.SSH_PORT }}
|
SSH_PORT: ${{ secrets.SSH_PORT }}
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
// API unreachable — serve stale cache
|
if (entry.ExpiresAt.Add(_staleGrace) > DateTime.UtcNow)
|
||||||
_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 (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()
|
||||||
|
|||||||
Reference in New Issue
Block a user