Hi,
mein Browsergame liefert mir derzeit immer wieder gerne einen OperationalError
:
OperationalError: (OperationalError) (1205, 'Lock wait timeout exceeded; try restarting transaction') 'UPDATE
Character
SET weapon_id=%s WHERECharacter
.id = %s' (10L, 1L)
Offenbar schließt er die Transaktion nicht richtig ab und das Feld bleibt gelockt.
Hintergrund: Mit der use
-Methode der Character-Instanz wird self.weapon = item
ausgeführt:
1 2 3 4 5 6 | if isinstance(item, Weapon): if item.twohanded: # reset offhand self.offhand = None self.weapon = item # other stuff via elif |
Weapon
ist eine von Item(Entity)
abgeleitete Klasse und self
dabei die Referenz auf eine Character
-Instanz (Character(Entity)
, wir befinden uns hier in Character.use(self, item)
), und die hat u.A. ManyToOne
-Attribute mit Namen weapon
und offhand
(die sich wiederrum auf Weapon(Item)
und Offhand(Item)
beziehen). In der (mit route
dekorierten) Funktion - in der, der char.use(item)
-Call stattfindet - folgt darauf unmittelbar session.commit()
und die Rückgabe des mit Werten gefüllten Templates.
Verwende ich use
um z.B. self.armor
(Armor(Item)
) zu setzen, habe ich den Fehler nicht. Der einzige Unterschied im Code ist dort, dass kein self.offhand
oder ähnliches überprüft werden muss (bei der Waffe brauche ich es um zu verhindern, dass man Zweihandwaffe und Schild trägt).
Wenn ich dann versuche eine weitere Datenbankabfrage zu erzeugen (z.B. ein Select) erhalte ich:
This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: (OperationalError) (1205, 'Lock wait timeout exceeded; try restarting transaction') 'UPDATE
Character
SET weapon_id=%s WHERECharacter
.id = %s' (10L, 1L)
Spätestens an der Stelle wird es unangenehm -.-' Kille ich den Server, ist das Problem weg. Allerdings lässt es sich nach wenigen Sekunden reproduzieren. Wenn ich ein Seitenladen abbreche, meldet bottle mir in der Konsole eine abgefangene Broken pipe
(soweit klar). Offensichtlich wird dabei der Thread (der für die Anfrage zuständig ist) gekillt. Allerdings bleibt die Datenbankaktion (z.B. mitten im gelockten Update) stehen und lässt das Feld gelockt. Was kann ich dagegen tun? 😕
Führe ich die gleichen Aktionen in der Python-Konsole aus, läuft alles fehlerfrei: keine Transaktionen blockieren oder sonstwas.
Um inhaltliche Denkfehler zu vermeiden, hier mein das Grobkonzept für das Zusammenspiel von bottle-Routen und DB-Transaktionen. Die bottle-Routen laufen inhaltlich so ab:
versuche Authentifizierung anhand Session-ID (
account.sid
vonAccount(Entity)
), sonst Umleitung auf Fehlerseiteführe die entsprechenden Dinge mit der Character-Instanz der eigenen Account-Instanz durch (
account.character
dient als Referenz) z.B. die ItemID aus dem Post lesen, das entsprechende Item dazu durch ein Query finden, prüfen obitem in character.inventory
und danncharacter.use(item)
.wenn alles erfolgreich, dann
session.commit()
und das entsprechende Template zurückgeben (gefüllt mit den Daten)wenn was schiefgeht:
session.rollback()
und den Fehler-Traceback in ne Log-File schreiben.
Hier mal noch der Codeausschnitt dazu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @post(u'/game/inventory/:sid/use/') def game_inventory_use(sid): try: player = Player.AuthPost(sid) item_id = int(request.POST[u'item_id']) # try use item item = Item.query.filter_by(id=item_id).first() if item is None or item not in player.character.inventory: return u'Ungültiges Item' try: player.character.use(item) return 'Nichts geschieht.' except Report as e: session.commit() return unicode(e) # event except Exception as e: if isinstance(e, HTTPResponse): raise session.rollback() syslog.log(e) redirect(u'/internal_error/') |
Zur Erklärung:
AuthPost
führt die Authentifizierung durch (Umleitung erfolgt innerhalb der Methode)das Try-Except-Block hat den Hintergrund, dass ich "Spielereignisse" (wie "Christian verwendet Kurzschwert.") von einer Funktion (mit einem zufälligen der gegebenen Texte) aus
Report(Exception)
erzeugen lassen (auch um aus der Funktion "schneller"). Ich habe mich gegenreturn
entschieden, da ichreturn
für "Nicht-Spielereignisse" verwende (sry wenn's unverständlich ist wegen der Exception 😀 ). Auf jeden Fall führt es (wenn ich einen string returne) zum gleichen Problem; scheint also nicht daran zu liegendas Abfangen aller weiteren Exceptions dient dem Loggen von Fehlern. Dazu hab ich ne Logger-Klasse die thread-safe in eine Datei schreibt. (das mit dem
isinstance(e, HTTPResponse)
brauche ich um bottle's redirects nicht fälschlicher Weise abzufangen, die sind offensichtlich auch von Exception abgeleitet)
LG Glocke
/EDIT: WUUUAHAHA longest post ever (für mich zumindest 😛 )