Compare commits

...
Author SHA1 Message Date
adminandClaude Opus 4.5 a201af2b65 Fix sync mismatch race condition in heartbeat response
The heartbeat sync check had a race condition:
1. Client sends heartbeat with current counts
2. New results arrive during round-trip, client updates counts
3. Server responds saying "you're behind" based on what was SENT
4. Client triggers unnecessary reconnect despite having caught up

This caused repeated sync mismatch reconnects during active Shardok
battles, which in turn caused large batch resyncs that made it
impossible to see the final battle actions before the battle ended.

The fix: when receiving a mismatch from the server, compare the
server's count against the client's CURRENT count. If the client
has caught up (client >= server), ignore the mismatch.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 07:03:13 -08:00
@@ -1467,30 +1467,61 @@ namespace eagle {
_remoteEagleClientLogger.LogLine(
$"[HEARTBEAT] Got response, server_timestamp={response.ServerTimestamp}");
// Check for sync mismatches reported by server
// Check for sync mismatches reported by server.
// IMPORTANT: The server's InSync flag is based on what the client SENT in the
// heartbeat. During the round-trip, the client might have received more results and
// caught up. We only trigger resync if the client's CURRENT count is still behind the
// server's count.
bool hasMismatch = false;
foreach (var syncResult in response.GameSyncResults) {
// Get current subscriber to check actual counts
IClientConnectionSubscriber subscriber = null;
lock (this) { _subscribers.TryGetValue(syncResult.GameId, out subscriber); }
if (!syncResult.EagleInSync) {
LogFlow($"SYNC_MISMATCH game={syncResult.GameId} server_count={syncResult.ServerUnfilteredResultCount}");
_remoteEagleClientLogger.LogLine(
$"[SYNC_MISMATCH] Game {syncResult.GameId}: Eagle out of sync, " +
$"server has {syncResult.ServerUnfilteredResultCount} results");
LogConnectionEvent(
"sync_mismatch_eagle",
$"game={syncResult.GameId}, server_count={syncResult.ServerUnfilteredResultCount}");
hasMismatch = true;
// Check current count - client may have caught up since heartbeat was sent
var clientCount = subscriber?.LastUnfilteredResultCount ?? 0;
var serverCount = syncResult.ServerUnfilteredResultCount;
if (clientCount >= serverCount) {
_remoteEagleClientLogger.LogLine(
$"[SYNC_MISMATCH] Game {syncResult.GameId}: Eagle caught up " +
$"(client={clientCount} >= server={serverCount}), ignoring");
} else {
LogFlow($"SYNC_MISMATCH game={syncResult.GameId} server_count={serverCount} client_count={clientCount}");
_remoteEagleClientLogger.LogLine(
$"[SYNC_MISMATCH] Game {syncResult.GameId}: Eagle out of sync, " +
$"server has {serverCount} results, client has {clientCount}");
LogConnectionEvent(
"sync_mismatch_eagle",
$"game={syncResult.GameId}, server_count={serverCount}, client_count={clientCount}");
hasMismatch = true;
}
}
foreach (var shardokResult in syncResult.ShardokSyncResults) {
if (!shardokResult.InSync) {
_remoteEagleClientLogger.LogLine(
$"[SYNC_MISMATCH] Game {syncResult.GameId}, Shardok {shardokResult.ShardokGameId}: " +
$"out of sync, server has {shardokResult.ServerFilteredResultCount} results");
LogConnectionEvent(
"sync_mismatch_shardok",
$"game={syncResult.GameId}, shardok={shardokResult.ShardokGameId}, " +
$"server_count={shardokResult.ServerFilteredResultCount}");
hasMismatch = true;
// Check current count - client may have caught up since heartbeat was sent
var serverCount = shardokResult.ServerFilteredResultCount;
var clientCount =
subscriber?.ShardokViewStatuses
.FirstOrDefault(
s => s.shardokGameId == shardokResult.ShardokGameId)
.filteredResultCount ??
0;
if (clientCount >= serverCount) {
_remoteEagleClientLogger.LogLine(
$"[SYNC_MISMATCH] Game {syncResult.GameId}, Shardok {shardokResult.ShardokGameId}: " +
$"caught up (client={clientCount} >= server={serverCount}), ignoring");
} else {
_remoteEagleClientLogger.LogLine(
$"[SYNC_MISMATCH] Game {syncResult.GameId}, Shardok {shardokResult.ShardokGameId}: " +
$"out of sync, server has {serverCount} results, client has {clientCount}");
LogConnectionEvent(
"sync_mismatch_shardok",
$"game={syncResult.GameId}, shardok={shardokResult.ShardokGameId}, " +
$"server_count={serverCount}, client_count={clientCount}");
hasMismatch = true;
}
}
}
}