Skip to content

Feature/game state recovery restart#109

Merged
tasaje1 merged 63 commits into
developmentfrom
feature/game-state-recovery-restart
May 20, 2026
Merged

Feature/game state recovery restart#109
tasaje1 merged 63 commits into
developmentfrom
feature/game-state-recovery-restart

Conversation

@tasaje1
Copy link
Copy Markdown
Collaborator

@tasaje1 tasaje1 commented May 19, 2026

Context

Implements persistent backend state storage to preserve active lobbies and games across backend restarts by automatically saving runtime snapshots after state changes.

Description

Added persistence infrastructure that stores the current in-memory system state whenever lobby or gameplay changes occur. Runtime snapshots now include active lobbies, player assignments, and ongoing game sessions, enabling recovery services to restore consistent state after restart.

Changes in the codebase

  • Added SystemStatePersistenceService to collect and persist current lobby and game snapshots
  • Added getLobbySnapshots() in LobbyService for serializable lobby state retrieval
  • Added getGameSnapshots() in GameService for serializable game state retrieval
  • Persisted state after lobby lifecycle events:
    • lobby creation
    • player join
    • player leave
    • role/team selection
    • game start
  • Persisted state after gameplay events:
    • clue submission
    • card reveal
    • turn passing
  • Integrated persistence handling into REST and WebSocket controllers
  • Simplified recovery flow by removing redundant GameRecoveryState
  • Updated game reconstruction to use GameStateDataTransferObject directly

Additional information

This implementation improves reliability by:

  • Persisting runtime state continuously instead of only during shutdown
  • Reducing data loss risk during unexpected backend termination
  • Keeping snapshot generation centralized through dedicated persistence services
  • Unifying persistence and recovery around existing DTO representations to avoid duplicate state mappings
  • Enabling restored games to continue with preserved turn state, clues, guesses, and revealed cards

@tasaje1 tasaje1 marked this pull request as draft May 19, 2026 10:07
@tasaje1 tasaje1 marked this pull request as ready for review May 19, 2026 23:56
Copy link
Copy Markdown
Collaborator

@the-only-queen-anna the-only-queen-anna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, ich habe mir das ganze durchgesehen, auch kurz mit AI darüber diskutiert, und habe folgende Bedenken:

  1. Bei jeder Aktion (Lobby erstellen, joinen, verlassen, Spiel starten, Hinweis geben, Karte aufdecken, ...) den ganzen Game State zu aktualisieren und als File neu zu speichern, ist extrem IO-aufwendig. Durch das ATOMIC_MOVE werden zwar wenigstens Konsistenz-Probleme vermieden, aber wir werden andere Probleme haben, weil wenn ich das richtig sehe, die Funktion keinen neuen Thread startet und daher bei den ganzen Requests blockierend wirkt. Das heißt, jede Request muss warten, bis der ganze Game State gespeichert worden ist, bevor sie verarbeitet wird.
  2. Eine JSON-File bringt uns auch nur was bei einem graceful shutdown oder wenn wirklich Spring Boot crasht, sobald der Container was abbekommt ist die File dann auch weg, schützt also nur mäßig.
  3. Warum nicht einfach eine simple Datenbank? Wäre vermutlich mit weniger Custom Code, der doch irgendwo auch fehleranfälliger ist, erledigt. Da müssten wir nur uns nur ein Datenbankschema überlegen.
  4. Eine Alternative für das ständige Updaten der Files wäre ein Thread, falls wir keine Datenbank wollen, der z.B. alle 30 Sekunden einen Snapshot macht und den dann in einem File speichert. Dann ersparen wir uns die Synchronisationsprobleme etc.
  5. Ich finde die Variante, wie die Lobbies wieder erstellt werden, etwas ineffizient. Jede Lobby einzeln neu erstellen, alle Spieler joinen, bei jedem Spieler doppelt den username auf null checken, und dann einzeln in die Liste schreiben dauert ewig, falls es keine RAM-Probleme macht hätte ich einfach die ganze Liste genommen, kopiert und dann als Ganzes wieder ausgetauscht, um bisschen Zeit zu sparen.

Also sollten wir als erstes prüfen, ob die Recovery-Variante zur Art von Ausfall passt, vor der wir uns schützen wollen, und dann nochmal überdenken, wie oft und was gesavet werden muss. Z.B. würde ich nur ingame-lobbies saven, die, die noch kein Spiel gestartet haben, können uns theoretisch auch egal sein (joinen und Rolle auswählen ist eine Sache von nicht mal 10 Sekunden).

@tasaje1
Copy link
Copy Markdown
Collaborator Author

tasaje1 commented May 20, 2026

Also, ich habe mir das ganze durchgesehen, auch kurz mit AI darüber diskutiert, und habe folgende Bedenken:

  1. Bei jeder Aktion (Lobby erstellen, joinen, verlassen, Spiel starten, Hinweis geben, Karte aufdecken, ...) den ganzen Game State zu aktualisieren und als File neu zu speichern, ist extrem IO-aufwendig. Durch das ATOMIC_MOVE werden zwar wenigstens Konsistenz-Probleme vermieden, aber wir werden andere Probleme haben, weil wenn ich das richtig sehe, die Funktion keinen neuen Thread startet und daher bei den ganzen Requests blockierend wirkt. Das heißt, jede Request muss warten, bis der ganze Game State gespeichert worden ist, bevor sie verarbeitet wird.
  2. Eine JSON-File bringt uns auch nur was bei einem graceful shutdown oder wenn wirklich Spring Boot crasht, sobald der Container was abbekommt ist die File dann auch weg, schützt also nur mäßig.
  3. Warum nicht einfach eine simple Datenbank? Wäre vermutlich mit weniger Custom Code, der doch irgendwo auch fehleranfälliger ist, erledigt. Da müssten wir nur uns nur ein Datenbankschema überlegen.
  4. Eine Alternative für das ständige Updaten der Files wäre ein Thread, falls wir keine Datenbank wollen, der z.B. alle 30 Sekunden einen Snapshot macht und den dann in einem File speichert. Dann ersparen wir uns die Synchronisationsprobleme etc.
  5. Ich finde die Variante, wie die Lobbies wieder erstellt werden, etwas ineffizient. Jede Lobby einzeln neu erstellen, alle Spieler joinen, bei jedem Spieler doppelt den username auf null checken, und dann einzeln in die Liste schreiben dauert ewig, falls es keine RAM-Probleme macht hätte ich einfach die ganze Liste genommen, kopiert und dann als Ganzes wieder ausgetauscht, um bisschen Zeit zu sparen.

Also sollten wir als erstes prüfen, ob die Recovery-Variante zur Art von Ausfall passt, vor der wir uns schützen wollen, und dann nochmal überdenken, wie oft und was gesavet werden muss. Z.B. würde ich nur ingame-lobbies saven, die, die noch kein Spiel gestartet haben, können uns theoretisch auch egal sein (joinen und Rolle auswählen ist eine Sache von nicht mal 10 Sekunden).

Mir wurde kommuniziert, dass eine Datenbank erst später geplant ist bzw. nicht mehr sinnvoll in diesen Sprint passt. Deshalb habe ich versucht, mit möglichst wenig zusätzlicher Infrastruktur zumindest Backend-Restarts besser abzufangen. Die Lösung ist also eher Recovery-Infrastruktur für den jetzigen Sprint, nicht als Ersatz für eine spätere Datenbank gedacht.

Member

Danke fürs Feedback, die Punkte versteh ich.

Die JSON-Lösung war hier eher als temporäre Recovery-Lösung gedacht, weil eine DB-Lösung soweit ich verstanden hab eher später geplant war und ich für den Sprint noch eine Möglichkeit einbauen wollte, Backend-Restarts abzufangen.

Ich seh aber die Nachteile auch voll vor allem I/O bei jeder Änderung und dass JSON natürlich keine langfristige Persistenz ersetzt. Für später wären asynchrone Snapshots oder eine DB viel sinnvoller.

Ich hab die Persistenz zusätzlich manuell getestet: Nach Lobby-Erstellung wird tatsächlich state.json erzeugt und gespeichert, Recovery lädt den Zustand beim Neustart wieder ein. Soll ich state.json bzw. den data Ordner committen ??

@the-only-queen-anna
Copy link
Copy Markdown
Collaborator

Nein, ich würde die Datei nicht committen, solange wir wissen, dass sie erstellt wird, sobald es states gibt, die persistiert werden müssen. Mein Vorschlag wäre, die Back-up Strukur so wie sie in deinem Code ist, zu übernehmen, aber halt nicht bei jeder Aktion zu updaten sondern alle 30-60 Sekunden, um I/O Aktivität zu sparen (ich weiß, bei uns ist es ja nur hypothetisch, aber dann würde es wesentlich besser skalieren, als bei jeder Aktion). Und ich gehe davon aus, dass wenn der Server zu tun kriegt, es immer viel auf einmal ist, deshalb auch bei wenigen aktiven Spielen gleichzeitig evtl. Verzögerungen auftreten können. Was hältst du von dieser Zwischenlösung?

Copy link
Copy Markdown
Collaborator

@the-only-queen-anna the-only-queen-anna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; danke für die Anpassungen. Das ist eine saubere Übergangslösung, bis wir in Sprint 3 die Datenbank haben.

org.junit.jupiter.api.Assertions.assertEquals(
SystemSnapshot.CURRENT_SCHEMA_VERSION, snapshot.schemaVersion());
org.junit.jupiter.api.Assertions.assertEquals(lobbySnapshots, snapshot.lobbies());
org.junit.jupiter.api.Assertions.assertEquals(gameSnapshots, snapshot.games());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Können wir bitte normale imports machen und asserEquals normal verwenden 🥹

XtophB
XtophB previously approved these changes May 20, 2026
Copy link
Copy Markdown
Collaborator

@XtophB XtophB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Habe Kommentar in einer Testklasse hinterlassen, können wir in dieser PR fixen oder im Post sprint refactoring. Dir überlassen.

@tasaje1 tasaje1 dismissed stale reviews from XtophB and the-only-queen-anna via 38c490d May 20, 2026 18:42
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Collaborator

@the-only-queen-anna the-only-queen-anna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown
Collaborator

@XtophB XtophB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tasaje1 tasaje1 merged commit 67c801a into development May 20, 2026
2 checks passed
@tasaje1 tasaje1 linked an issue May 20, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wiederherstellung des Spielzustands bei Neustart des Docker-Containers

3 participants