Skip to content

Commit 6eac71c

Browse files
committed
fix timeout
1 parent 7bd3fe2 commit 6eac71c

2 files changed

Lines changed: 87 additions & 6 deletions

File tree

Backgammon.Server/Services/GameSession.cs

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,20 @@ public void StartTimeUpdates(IHubContext<Hubs.GameHub, IGameHubClient> hubContex
491491
{
492492
_timeUpdateTimer?.Dispose();
493493
_timeUpdateTimer = new System.Threading.Timer(
494-
async _ => await BroadcastTimeUpdate(hubContext),
494+
async _ =>
495+
{
496+
try
497+
{
498+
await BroadcastTimeUpdate(hubContext);
499+
}
500+
catch (Exception ex)
501+
{
502+
Console.WriteLine($"[TIME DEBUG] CRITICAL ERROR: Game {Id}: Unhandled exception in time update loop - {ex.GetType().Name}: {ex.Message}");
503+
Console.WriteLine($"[TIME DEBUG] Stack trace: {ex.StackTrace}");
504+
Console.WriteLine($"[TIME DEBUG] Stopping time updates to prevent server crash.");
505+
StopTimeUpdates();
506+
}
507+
},
495508
null,
496509
TimeSpan.FromSeconds(1),
497510
TimeSpan.FromSeconds(1));
@@ -680,8 +693,26 @@ private int CalculatePipCount(CheckerColor color)
680693
/// </summary>
681694
private async Task BroadcastTimeUpdate(IHubContext<Hubs.GameHub, IGameHubClient> hubContext)
682695
{
696+
// DEFENSIVE: Stop time updates if game is already over
697+
if (Engine.GameOver)
698+
{
699+
Console.WriteLine($"[TIME DEBUG] Game {Id}: Time update called but game is over. Stopping time updates.");
700+
StopTimeUpdates();
701+
return;
702+
}
703+
683704
if (Engine.WhiteTimeState == null || Engine.RedTimeState == null || TimeControl == null)
684705
{
706+
Console.WriteLine($"[TIME DEBUG] Game {Id}: Time update called but time state or time control is null. Stopping updates.");
707+
StopTimeUpdates();
708+
return;
709+
}
710+
711+
// DEFENSIVE: Don't run time updates for games without time control
712+
if (TimeControl.Type == TimeControlType.None)
713+
{
714+
Console.WriteLine($"[TIME DEBUG] Game {Id}: No time control enabled. Stopping time updates.");
715+
StopTimeUpdates();
685716
return;
686717
}
687718

@@ -730,19 +761,50 @@ private async Task BroadcastTimeUpdate(IHubContext<Hubs.GameHub, IGameHubClient>
730761
/// </summary>
731762
private async Task HandleTimeout(IHubContext<Hubs.GameHub, IGameHubClient> hubContext)
732763
{
764+
// DEFENSIVE: Don't try to forfeit if game is already over
765+
if (Engine.GameOver)
766+
{
767+
Console.WriteLine($"[TIME DEBUG] Game {Id}: Timeout triggered but game already over. Skipping forfeit.");
768+
return;
769+
}
770+
771+
// DEFENSIVE: Additional safety check for time control
772+
if (TimeControl == null || TimeControl.Type == TimeControlType.None)
773+
{
774+
Console.WriteLine($"[TIME DEBUG] Game {Id}: Timeout called but no time control enabled. Skipping forfeit.");
775+
return;
776+
}
777+
733778
var losingPlayer = Engine.CurrentPlayer;
734779
var winningPlayer = Engine.GetOpponent();
735780

736781
if (losingPlayer == null || winningPlayer == null)
737782
{
738-
Console.WriteLine("[TIME DEBUG] ERROR: Cannot handle timeout - player is null");
783+
Console.WriteLine($"[TIME DEBUG] ERROR: Game {Id}: Cannot handle timeout - player is null (CurrentPlayer: {losingPlayer?.Color.ToString() ?? "null"}, Opponent: {winningPlayer?.Color.ToString() ?? "null"})");
739784
return;
740785
}
741786

742-
Console.WriteLine($"[TIME DEBUG] TIMEOUT! {losingPlayer.Color} ran out of time. {winningPlayer.Color} wins!");
787+
Console.WriteLine($"[TIME DEBUG] Game {Id}: TIMEOUT! {losingPlayer.Color} ran out of time. {winningPlayer.Color} wins by forfeit.");
743788

744-
// Mark game as over using ForfeitGame
745-
Engine.ForfeitGame(winningPlayer);
789+
try
790+
{
791+
// Mark game as over using ForfeitGame
792+
Engine.ForfeitGame(winningPlayer);
793+
}
794+
catch (InvalidOperationException ex)
795+
{
796+
// Game state was invalid - log and return without crashing
797+
Console.WriteLine($"[TIME DEBUG] ERROR: Game {Id}: Failed to forfeit game on timeout - {ex.Message}. Game may already be over or in invalid state.");
798+
StopTimeUpdates();
799+
return;
800+
}
801+
catch (Exception ex)
802+
{
803+
// Unexpected exception - log and return without crashing the server
804+
Console.WriteLine($"[TIME DEBUG] ERROR: Game {Id}: Unexpected error while handling timeout - {ex.GetType().Name}: {ex.Message}");
805+
StopTimeUpdates();
806+
return;
807+
}
746808

747809
// Broadcast timeout event
748810
var timeoutEvent = new PlayerTimedOutDto

Backgammon.Server/Services/GameSessionManager.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,22 @@ public async Task LoadActiveGamesAsync(IGameRepository repository)
286286
{
287287
try
288288
{
289+
// DEFENSIVE: Skip games that are completed or abandoned (shouldn't be in active games, but just in case)
290+
if (gameData.Status == "Completed" || gameData.Status == "Abandoned")
291+
{
292+
Console.WriteLine($" ⊘ Skipping completed/abandoned game {gameData.GameId} (Status: {gameData.Status})");
293+
continue;
294+
}
295+
289296
var session = GameEngineMapper.FromGame(gameData);
290297

298+
// DEFENSIVE: Skip games with invalid state (e.g., GameOver=true for "InProgress" status)
299+
if (session.Engine.GameOver)
300+
{
301+
Console.WriteLine($" ⊘ Skipping game {gameData.GameId}: Status is InProgress but game engine reports GameOver=true");
302+
continue;
303+
}
304+
291305
// Add to in-memory storage
292306
_games[session.Id] = session;
293307

@@ -300,11 +314,16 @@ public async Task LoadActiveGamesAsync(IGameRepository repository)
300314
$"Red={session.RedPlayerName ?? "waiting"}, " +
301315
$"Current={session.Engine.CurrentPlayer?.Name ?? "unknown"}");
302316
}
303-
catch (Exception ex)
317+
catch (InvalidOperationException ex)
304318
{
305319
failureCount++;
306320
Console.WriteLine($" ✗ Failed to load game {gameData.GameId}: {ex.Message}");
307321
}
322+
catch (Exception ex)
323+
{
324+
failureCount++;
325+
Console.WriteLine($" ✗ Failed to load game {gameData.GameId}: Unexpected error - {ex.GetType().Name}: {ex.Message}");
326+
}
308327
}
309328

310329
Console.WriteLine($"[GameSessionManager] Successfully loaded {successCount} games ({failureCount} failures)");

0 commit comments

Comments
 (0)