ubuntuusers.de

Python, bottle, Elixir - Frage zum "richtigen" Zusammenspiel

Status: Gelöst | Ubuntu-Version: Ubuntu 12.04 (Precise Pangolin)
Antworten |

Glocke

Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

Wohnort: Thüringen

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 WHERE Character.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 WHERE Character.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 von Account(Entity)), sonst Umleitung auf Fehlerseite

  • fü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 ob item in character.inventory und dann character.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 gegen return entschieden, da ich return 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 liegen

  • das 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 😛 )

Glocke

(Themenstarter)
Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

Wohnort: Thüringen

Hab die Geschichte mit den (quasi unnötigen) Exceptions jetzt umgeschrieben (die "Ereignisse" in String-Form werden mittels return nun zurückgegeben).

Das genannte Problem besteht weiterhin ☹

LG Glocke

Glocke

(Themenstarter)
Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

Wohnort: Thüringen

*push*

Zusammenfassend: Das Problem scheint aus meiner Sicht an 2 Punkten zu liegen:

LG Glocke

Glocke

(Themenstarter)
Avatar von Glocke

Anmeldungsdatum:
1. März 2009

Beiträge: 880

Wohnort: Thüringen

Problem gelöst.

Hilfreich war ein Funktions-Dekorator, der die Exception-Geschichte, das Logging übernimmt und die Session committet oder zurückrollt. Außerdem habe ich auf view-Dekoratoren verzichtet und die Templates innerhalb der Funktion gefüllt, so dass sie vom genannten Dekorator mit verwaltet wird.

LG

Antworten |