Auto-invalidate game cache when flush marker is updated (#5273)

During warmup, the staging server may cache stale game data that was
loaded before the active server flushed. Instead of exposing an RPC
for cache invalidation (which leaks internal state), the server now
automatically detects when the flush marker is updated and invalidates
any cached games.

Changes:
- GamesManager: Added `invalidateCacheIfFlushMarkerUpdated()` that
  checks the flush marker's modification time and clears the cache
  if it's been updated since the last check
- Called before checking if a game is in cache, so stale data is
  cleared before any attempt to use it
- Removed the InvalidateGameCache RPC (no longer needed)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-13 08:04:56 -08:00
committed by GitHub
co-authored by Claude Opus 4.5
parent 6da0636731
commit 3ae3f6ba16
2 changed files with 45 additions and 1 deletions
+2 -1
View File
@@ -304,9 +304,10 @@ main() {
# Step 6: Create flush marker - signals that disk state is fresh
# Any waiting lazy-loads on staging will now proceed with fresh data.
# The Eagle server automatically detects the flush marker update and invalidates any stale cached games.
log_info "[DEPLOY:${deploy_id}] Step 6: Creating flush marker..."
create_flush_marker "${deploy_id}" "eagle-${staging}"
log_info "[DEPLOY:${deploy_id}] Flush marker created - waiting lazy-loads can now proceed"
log_info "[DEPLOY:${deploy_id}] Flush marker created - server will auto-invalidate stale cache"
# Update .env for admin service
local env_file="${APP_DIR}/.env"
@@ -261,6 +261,45 @@ class GamesManager(
shardokClient.connect()
}
// Track the flush marker mod time to detect when it's been updated
@volatile private var lastFlushMarkerCheck: Long = 0L
@volatile private var flushMarkerModTime: Long = 0L
/**
* Checks if the flush marker has been updated since we last loaded games. If so, invalidates the cache so games
* reload from fresh disk state.
*
* This handles blue-green deployments automatically: if games were cached during warmup (before the active server
* flushed), we detect the new flush marker and clear the cache. No external RPC needed.
*/
private def invalidateCacheIfFlushMarkerUpdated(): Unit = {
val now = System.currentTimeMillis()
// Only check every 100ms to avoid excessive file system access
if now - lastFlushMarkerCheck < 100 then return
lastFlushMarkerCheck = now
val flushMarker = java.nio.file.Paths.get(SaveDirectory.saveDirectory, ".flush_complete")
if !java.nio.file.Files.exists(flushMarker) then return
try {
val currentModTime = java.nio.file.Files.getLastModifiedTime(flushMarker).toMillis
if currentModTime > flushMarkerModTime then {
val cachedCount = gameControllerInfos.size
if cachedCount > 0 then {
SimpleTimedLogger.printLogger.logLine(
s"[DEPLOY] Flush marker updated, invalidating $cachedCount cached games"
)
gameControllerInfos = Map.empty
gamesAwaitingPlayers = Vector.empty
removedGameIds = Set.empty
}
flushMarkerModTime = currentModTime
}
} catch {
case _: Exception => // Ignore errors checking marker
}
}
/**
* Reads the current games.e0es file from disk to get metadata about running games. Called fresh each time to handle
* race conditions during blue-green deployment (e.g., a game created on the old server after the new server started).
@@ -370,6 +409,10 @@ class GamesManager(
* true if game is now loaded (or was already loaded), false if game doesn't exist
*/
private def ensureGameLoaded(gameId: GameId): Boolean = {
// Check if flush marker was updated - if so, invalidate any stale cached games
// This handles the case where games were cached during warmup before the active server flushed
invalidateCacheIfFlushMarkerUpdated()
if gameControllerInfos.contains(gameId) then return true
// Wait for flush marker before loading from disk to ensure we get fresh data