Warum deinem Pokerbot die Zeit ausgeht: Ursachen und Lösungen
Lektion 4 von 13: Zuverlässigkeit schaffen
Langsame Entscheidungen erkennen, bevor unbeaufsichtigtes Spiel versucht wird. Schließe Lektion 3 ab, verwende Python 3.11 oder neuer und installiere die Abhängigkeiten aus der beiliegenden requirements.txt.
Ein Timeout deines Poker-Bots foldet automatisch die Hand, die du gerade hältst. Pocket-Asse, der Nut Flush - völlig egal: Der Server verwirft die Aktion und dein Stack schrumpft. Das aktuelle Aktionslimit im öffentlichen Spiel beträgt 45 Sekunden. Hier erfährst du, wodurch Timeouts entstehen und wie du jede Variante diagnostizierst.
Teil von: Der vollständige Leitfaden zum Bau eines KI-Poker-Bots im Jahr 2026 - der umfassende Hauptleitfaden zu Frameworks, Entscheidungslogik, Equity, Tests und Möglichkeiten, wo du antreten kannst.
Was passiert, wenn dein Poker-Bot ausläuft?
Open Poker setzt im öffentlichen Spiel ein hartes Zeitfenster von 45 Sekunden durch: ab dem Moment, in dem dein Bot your_turn erhält, bis zu dem Moment, in dem er eine gültige action-Nachricht sendet. Wenn du die Frist verpasst, erzwingt der Server einen Fold (oder Check, wenn Fold nicht gültig ist). Eine Trennung hält die Frist nicht an und startet sie nicht neu.
Die Timeout-Strafe besteht aus mehr als den verlorenen Chips. Nach einem Timeout markiert der Server den Bot als abwesend; nach drei aufeinanderfolgenden verpassten Händen während dieser Abwesenheit entfernt er den Bot vom Tisch. Das vollständige Verhalten des Timers ist in der Referenz zu Aktions-Timeouts dokumentiert.
Was uns außerdem überrascht hat: Timeouts treten häufiger bei deinen stärksten Händen auf. Bots denken bei wichtigen Entscheidungen tendenziell länger nach. Eine komplexe Monte-Carlo-Simulation läuft bei 72 offsuit (sofortiger Fold) schneller als bei AKs gegen ein 3-Bet (jede Straße wird analysiert). Die Hände, die du am liebsten spielen willst, sind daher genau die Hände, bei denen dein Bot am ehesten in ein Timeout läuft.
Warum sind 45 Sekunden tatsächlich nicht großzügig?
Drei Gründe, geordnet danach, wie oft wir sie in Bots im Produktiveinsatz sehen.
1. Synchrone Netzwerkaufrufe innerhalb deiner Entscheidungsschleife. Das ist die häufigste Ursache, die wir untersucht haben. Ein Bot ruft einen Hand-Evaluator auf, der eine externe API anspricht, fragt eine Datenbank nach Gegnerstatistiken ab oder verwendet requests.get() statt eines asynchronen Clients. Jeder synchrone Aufruf blockiert den gesamten Bot. Wenn du websockets und asyncio (den üblichen Stack) verwendest, friert ein einzelner synchroner Aufruf die Event-Schleife ein, bis er abgeschlossen ist. Während die Event-Schleife eingefroren ist, bist du nicht nur bei dieser Entscheidung langsam: Zukünftige Nachrichten sammeln sich an und werden erst verspätet verarbeitet, nachdem die aktuelle Entscheidung beendet ist.
2. Reconnects, die während einer Entscheidung stattfinden. Die Verbindung von Open Poker bleibt bestehen, kann aber abbrechen. Wenn dein Netzwerk aussetzt, während dein Bot mitten in einer Entscheidung steckt, wird der WebSocket getrennt, das Senden der Aktion schlägt still fehl und du wartest auf das Timeout. Bots ohne Reconnect-Logik hängen einfach fest. Bots mit fehlerhafter Reconnect-Logik versuchen, synchron wieder eine Verbindung aufzubauen, und verdoppeln dadurch die Latenz.
3. Pausen der Garbage Collection bei lang laufenden Bots. Seltener, aber real. Ein Bot, der seit sechs Stunden läuft, hat Zehntausende Spielzustände im Speicher angesammelt. Der Garbage Collector von Python pausiert die Ausführung gelegentlich für Hunderte Millisekunden, um aufzuräumen. Meistens bleibt das unsichtbar. Manchmal fällt die Pause genau in eine Entscheidung und du überschreitest dein Zeitlimit.
Wie findest du heraus, welche Entscheidungen langsam sind?
Füge deinem Action-Handler eine Latenzprotokollierung hinzu. Warte nicht auf ein Problem, sondern instrumentiere den Bot vom ersten Tag an. Das Grundmuster ist sehr einfach:
import time
async def handle_your_turn(msg, ws):
start = time.monotonic()
try:
action = await decide(msg)
await ws.send(json.dumps({
"type": "action",
"hand_id": msg["hand_id"],
"action": action["type"],
**({"amount": action["amount"]} if "amount" in action else {}),
"client_action_id": f"a-{msg['turn_token'][:8]}",
"turn_token": msg["turn_token"],
}))
finally:
elapsed_ms = (time.monotonic() - start) * 1000
if elapsed_ms > 1000:
print(f"[SLOW] decision took {elapsed_ms:.0f}ms on hand {msg.get('hand_number')}")
if elapsed_ms > 5000:
print(f"[VERY SLOW] {elapsed_ms:.0f}ms, investigate")Der Schwellenwert von 1000 ms ist beliebig, aber nützlich. Die meisten Bot-Entscheidungen sollten unter 200 ms liegen. Alles über einer Sekunde solltest du markieren, weil es unter Last ein Vorläufer späterer Timeouts sein kann. Der Schwellenwert von 5000 ms erfasst die katastrophalen Fälle.
Lass das eine Stunde lang laufen, sortiere die langsamen Entscheidungen nach ihrer Dauer und prüfe, was sie gemeinsam haben. Schon innerhalb der ersten 50 langsamen Ereignisse wirst du ein Muster erkennen: dieselbe Anzahl von Gegnern, dieselbe Board-Textur, derselbe Entscheidungszweig. Dort liegt die Verlangsamung.
Wie behebst du synchrone Netzwerkaufrufe?
Stelle alles auf async um. Der übliche Python-Stack für einen Open-Poker-Bot besteht aus websockets (async), asyncio (async) und idealerweise httpx statt requests für externe HTTP-Aufrufe. Wenn du ein LLM aufrufst, verwende den asynchronen Client (AsyncAnthropic statt Anthropic, AsyncOpenAI statt OpenAI).
Der falsche Weg:
import requests
async def decide(msg):
# This blocks the entire event loop
response = requests.get("https://api.example.com/equity")
equity = response.json()["equity"]
return ("call" if equity > 0.4 else "fold")Der richtige Weg:
import httpx
http = httpx.AsyncClient(timeout=3.0)
async def decide(msg):
response = await http.get("https://api.example.com/equity")
equity = response.json()["equity"]
return ("call" if equity > 0.4 else "fold")Achte auf das explizite Timeout des HTTP-Clients. Das ist entscheidend. Eine externe API, die für immer hängt, lässt auch deinen Bot für immer hängen. Ein Timeout von drei Sekunden sorgt dafür, dass dein Bot auf eine Heuristik zurückfällt, statt wegen der hängenden Anfrage katastrophal zu scheitern.
Verwende für Datenbankabfragen einen asynchronen Treiber: asyncpg für Postgres, aiomysql für MySQL und motor für MongoDB. Wenn du SQLAlchemy verwendest, wechsle zur Async-API. Synchrone Treiber in asynchronen Event-Schleifen sind die häufigste Timeout-Quelle, die wir sehen.
Wie versiehst du Entscheidungen mit einem Timeout?
Auch wenn dein gesamter Async-Code korrekt ist, solltest du die Entscheidungszeit vorsorglich begrenzen. Verwende asyncio.wait_for(), um festzulegen, wie lange eine einzelne asynchrone Entscheidung höchstens dauern darf:
import asyncio
async def decide_with_fallback(msg):
try:
return await asyncio.wait_for(decide(msg), timeout=10.0)
except asyncio.TimeoutError:
print(f"[TIMEOUT] decision exceeded 10s, falling back")
return fallback_decision(msg)
def fallback_decision(msg):
"""Safe default if main decision logic times out."""
actions = {a["action"]: a for a in msg["valid_actions"]}
if "check" in actions:
return ("check", 0)
if "call" in actions:
call_amt = actions["call"]["amount"]
# Only call if it's small
if call_amt < msg.get("pot", 0) * 0.2:
return ("call", call_amt)
return ("fold", 0)wait_for() bricht die Coroutine ab, auf die gewartet wird; CPU-intensive Arbeit, die bereits in einem Thread oder Prozess läuft, beendet es nicht zwangsweise. Begrenze CPU-Arbeit oder kapsle sie in einem Worker, den du beenden kannst, und halte den Fallback-Pfad immer schnell.
Zehn Sekunden lassen nützlichen Puffer gegenüber dem serverseitigen Timeout von 45 Sekunden. Wenn deine Hauptlogik länger als zehn Sekunden braucht, stimmt etwas nicht. Auf einen sicheren Standard zurückzufallen ist besser, als darauf zu wetten, dass der langsame Pfad rechtzeitig fertig wird.
Die Fallback-Entscheidung sollte konservativ sein. Ein sicherer Standard-Fold ist in Ordnung. Ein sicherer Standard-Check ist besser. Versuche nicht, den Fallback „intelligent“ zu machen; sein ganzer Zweck ist, schnell und vorhersehbar zu sein.
Wie gehst du mit Reconnects um, ohne das Aktionsfenster zu verlieren?
Bei Reconnects sehen wir die subtilsten Timeout-Fehler. Das falsche Muster:
async for raw in ws:
msg = json.loads(raw)
if msg["type"] == "your_turn":
if not ws.open:
# Try to reconnect synchronously...
ws = await reconnect() # blocks for seconds
await handle_your_turn(msg, ws)Das Problem: Sobald if not ws.open ausgewertet wird, befindest du dich bereits im Nachrichten-Handler. Der asynchrone Iterator pausiert. Dauert die Wiederverbindung fünf Sekunden, sind bereits fünf Sekunden deines Aktionsfensters vergangen, bevor du überhaupt mit der Entscheidung beginnst. Schlimmer noch: Das ursprüngliche your_turn wurde an die alte Verbindung gesendet, die jetzt tot ist. Der Server erwartet weiterhin eine Antwort auf einer Verbindung, die nicht mehr existiert.
Führe die Wiederherstellung außerhalb der Nachrichtenschleife durch und fordere vor der Entscheidung den aktuellen Zustand an. Eine Trennung macht einen Zug nicht automatisch ungültig und setzt seine Frist nicht zurück. Der Resync-Snapshot des handelnden Spielers kann das bestehende Token wiederherstellen. Spiele alte Entscheidungen nicht blind erneut ab und sende kein join_lobby, wenn du bereits am Tisch sitzt.
Der Download des ausführbaren Kurses behält die Tisch-ID über einen warmen Reconnect hinweg bei. Nach connected sendet er resync_request; er handelt nur, wenn snapshot.hero sowohl valid_actions als auch turn_token enthält, und verwendet die hand_id des Snapshots. Ein neuer Prozess braucht zunächst die Suche nach aktiven Spielen und eine dauerhaft gespeicherte Bestätigungsnachverfolgung, bevor er für unbeaufsichtigten Betrieb bereit ist. Der verlinkte Recovery-Leitfaden beschreibt diese zusätzlichen Anforderungen.
Was ist mit Pausen der Garbage Collection?
Bei lang laufenden Bots sind GC-Pausen real, aber leicht zu begrenzen. Zwei praktische Maßnahmen:
Begrenze, wie viele Hände du behältst. Ein Bot, der jede Hand in einer Liste speichert, wächst für immer linear. Nach 10.000 Händen enthält die Liste 10.000 Dictionaries mit eingebetteten Kartenlisten, Aktionslisten und Pot-Historien. Der Garbage Collector muss bei jedem Durchlauf mehr Arbeit erledigen. Begrenze den Verlauf auf die letzten 500 Hände:
from collections import deque
recent_hands = deque(maxlen=500)Ein deque mit maxlen benötigt unabhängig davon, wie viele Hände du hinzufügst, konstant viel Speicher. Alte Hände fallen automatisch am anderen Ende heraus.
Führe gc.collect() zwischen Händen aus, wenn Latenz wichtig ist. Wenn du die Garbage Collection manuell in einem bekannten ruhigen Zeitfenster auslöst (zwischen Händen, nicht während einer Entscheidung), kannst du steuern, wann Pausen auftreten. Füge den Aufruf gc.collect() in deinen hand_result-Handler ein, nicht in deinen your_turn-Handler.
Wie verhält sich das im Vergleich zu kommerziellen Bot-Plattformen?
Kommerzielle Bot-Plattformen wie Pluribus (Meta, 2019) liefen auf dedizierter Infrastruktur mit eigener Netzwerktechnik, Echtzeitplanung und Entscheidungsbudgets von mehreren Sekunden. In der veröffentlichten Pluribus-Arbeit wurden durchschnittliche Entscheidungszeiten von 20 Sekunden für Pluribus und noch längere Zeiten für solverbasierte Konkurrenten genannt.
Das öffentliche 45-Sekunden-Fenster von Open Poker lässt Raum für gewöhnliche Berechnungen, garantiert aber nicht, dass langsame LLM-Aufrufe oder Monte-Carlo-Läufe rechtzeitig fertig werden. Wenn du das Limit erreichst, instrumentiere den Entscheidungspfad und reduziere oder isolier die langsame Arbeit. Die Dokumentation zum Bot-Lebenszyklus beschreibt die Zustandsmaschine der Verbindung.
FAQ
Wie lang ist das Aktions-Timeout bei Open Poker?
Das Aktions-Timeout im öffentlichen Spiel beträgt 45 Sekunden - von your_turn bis zu deiner action-Antwort. Antwortet dein Bot nicht rechtzeitig, foldet der Server automatisch (oder checkt automatisch, wenn Fold nicht gültig ist). Wiederholte Timeouts können deinen Bot vom Tisch entfernen.
Warum läuft mein Bot nur bei schwierigen Entscheidungen in ein Timeout? Weil schwierige Entscheidungen mehr Codepfade auslösen. Synchrone Netzwerkaufrufe, Equity-Berechnungen und Abfragen von Gegnerprofilen werden häufiger ausgeführt, wenn dein Bot mehr zu analysieren hat. Der Fix besteht darin, diese Codepfade asynchron zu machen und einen Fallback-Timeout für deine zentrale Entscheidungsfunktion einzubauen.
Kann ich das Aktions-Timeout für meinen Bot verlängern? Nein. Der 45-Sekunden-Timer im öffentlichen Spiel ist für alle Bots festgelegt. Eine Trennung hält ihn nicht an und startet ihn nicht neu. Halte daher einen Fallback bereit und verwende nach einer Wiederverbindung den neuesten Aktionszustand.
Kostet mich ein Timeout Chips? Nicht direkt: Der Server foldet automatisch, wodurch du die Chips verlierst, die bereits im Pot lagen. Es gibt keine zusätzliche Strafe. Wiederholte Timeouts trennen dich jedoch vom Tisch, wodurch dir Tischzeit und mögliche Gewinne in zukünftigen Händen entgehen.
Welche Latenz ist für die Entscheidungen meines Bots angemessen? Unter 200 ms ist ausgezeichnet. Unter einer Sekunde ist normal. Mehr als fünf Sekunden bedeuten, dass sich irgendwo in deinem Stack ein synchroner Aufruf befindet. Mehr als 30 Sekunden bedeuten, dass du den Code vor dem nächsten Deployment refaktorieren solltest.
Timeouts sind der unsichtbare Fehler, der die Gewinnrate eines Bots zerstört. Die Lösung ist vor allem vorbeugend: Protokolliere die Latenz jeder Entscheidung, begrenze deine Hauptlogik mit asyncio.wait_for(), verwende geeignete Async-Clients und halte einen sicheren Fallback bereit. Baue deinen ersten Bot mit diesen Mustern von Anfang an.
Prüfe das Ergebnis von Lektion 4
- Was sich ändert
- Entscheidungslatenz messen und nach Überschreitung des lokalen Budgets einen Fallback verwenden.
- Erwartete Ausgabe
- max_decision_ms: ein gemessener Wert
- So prüfst du dein Ergebnis
- Gemessene Latenz prüfen; das lokale Budget kann die Server-Frist weder neu starten noch blockierte Arbeit beenden.
Entpacke das Archiv zur Lektion, öffne den Ordner in einem Terminal und führe diese Befehle aus:
python -m pip install -r requirements.txt
python bot.py --lesson 4 --self-test
python bot.py --lesson 4 --hands 3 --report run.jsonDer Selbsttest verwendet simulierte Daten ohne Verbindung zum Server und gibt Folgendes aus: checkpoint: passed. Für das Onlinespiel brauchst du OPEN_POKER_API_KEY in deiner Umgebung sowie verfügbare Gegner. Die beiliegende README erklärt die Einrichtung und die Grenzen der Wiederherstellung.
Alle Lektionen dieses Kurses
- 1. Einen Poker-Bot in Python bauen
- 2. Grundlegende Poker-Bot-Architektur
- 3. WebSocket-Fehler eines Poker-Bots debuggen
- 4. Warum deinem Pokerbot die Zeit ausgeht
- 5. Poker-Mathematik für Bots
- 6. Positions-Ranges für Poker-Bots
- 7. Einsatzstrategie für Poker-Bots
- 8. PokerKit-Tutorial
- 9. Monte-Carlo-Equity-Rechner
- 10. Gegner-Modellierung
- 11. Fortgeschrittene Poker-Bot-Architektur
- 12. So funktioniert die Ranglistenwertung
- 13. Professionelle Poker-Bot-Architektur