Seite 1 von 1
[Bottle] Clean-Up nach Broken Pipe
Verfasst: Mittwoch 29. August 2012, 20:53
von glocke
Hi,
ich suche eine Möglichkeit - nach einer Broken Pipe (nehmen wir mal an durch das Abbrechen eines Seite-Ladens) - noch etwas zu tun - z.B. was aufzuräumen. Hintergrund ist, dass (in meinem Browsergame-Projekt) eine DB-Abfrage (Elixir bzw. SQL-Alchemy in Verbindung mit MySQL) bei einer UPDATE-Transaktion das jeweilige Feld vorher sperrt und nach Beendigung des Schreibens wieder entsperrt. Blöderweise passiert es immermal, dass - wenn das Laden der Seite / des Ajax-Requests abgebrochen wird - die Transaktion nicht komplett ist und das jeweilige Feld gesperrt bleibt. Ich habe im Datenbank-Unterforum das Problem bereits formuliert (
http://www.python-forum.de/viewtopic.php?f=23&t=29933), allerdings denke ich, dass es eher an bottle liegt (zumindest die Sache mit dem "etwas nach einer Broken Pipe tun").
Oder vereinfacht ausgedrückt folgender Code:
Code: Alles auswählen
from bottle import route, run
from threading import Lock
field_lock = Lock() # sei mal der Lock auf dem zu verwendenden Datenfeld
# Dekorator, um automatisch die Session zu commiten oder zurückzurollen
def managed_transaction(func):
def _managed_transaction(*args, **kw):
try:
res = func(*args, **kw)
print 'Commit!'
except Exception as e:
print 'Rollback!'
return res
return _managed_transaction
# eine "fehlerfreie" Transaktion
@route('/complete/')
@managed_transaction
def complete():
print 'Waiting'
with field_lock:
print 'Locked!'
print 'Accessed' # das wäre der eigentliche Schreib-Zugriff
print 'Unlocked!'
# eine sehr langwierige Transaktion (warum auch immer sie in der Praxis so lange dauern mag)
@route('/incomplete/')
@managed_transaction
def incomplete():
print 'Waiting'
with field_lock:
print 'Locked!'
while True: # das Schreiben verzögert sich (warum auch immer) sehr lange - hier mal extrem als Endlosschleife
pass
print 'Accessed'
print 'Unlocked!'
run()
Führe ich das Skript nun aus und gehe auf 'localhost:8080/complete/' erscheint in der Konsole:
Gehe ich dann auf die 'incomplete'-Route erscheint nur ein
Nun muss ich sie im Browser abbrechen (ist ja ne Endlosschleife). Genau das entspricht dem Original-Szenario. Im Code müsste (da will ich hin) nun eine "Behandlung" der Broken Pipe kommen (um die Session zurückzurollen und den Lock aufzuheben). Allerdings finde ich da keine Möglichkeit im bottle-Framework. Führe ich die 'complete'-Route aus, bleibt er nun ebenfalls stehen (der Lock ist ja gesetzt!)
Ich hoffe, ich habe es möglichst verständlich beschrieben
LG Glocke
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Mittwoch 29. August 2012, 23:32
von deets
Eine Moeglichkeit, die ich sehe: der Transaktionsdekorator, den ich dir vorgeschlagen haben, hat eine Schwaeche: er funktioniert ausschliesslich um die Methode herum.
Wenn du aber zB das Bottle Templating System benutzt, und DB-Objekte zurueckgibst in deiner Action, die dann *nach* dem Dekorator das Template befuellen - dann ist der Session-Zustand irgendwie undefiniert und koennte solche Probleme verursachen.
Versuch doch mal, den Transaktionsdekorator um einen Middleware herum zu stricken, die du dann um die bottle-WSGI-app legst. Und diese gewrappte Middleware (du koenntest zB dafuer repoze.tm2 benutzen, das integriert sich mit einer Extension mit SA/Elixir) ist dann das, was du vom bottle-server oder deinem WSGI-Container ausliefern laesst.
Die broken Pipes sollten dann eigentlich nicht mehr wirklich ein Problem darstellen - ausser du machst was mit generatoren, aber dann wird's kompliziert.
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Mittwoch 29. August 2012, 23:46
von glocke
Hi,
ich verwende die SimpleTemplate Engine von Bottle.
deets hat geschrieben:Wenn du aber zB das Bottle Templating System benutzt, und DB-Objekte zurueckgibst in deiner Action, die dann *nach* dem Dekorator das Template befuellen - dann ist der Session-Zustand irgendwie undefiniert und koennte solche Probleme verursachen.
Du meinst, wenn ich den view-Dekorator verwende? Da stimme ich dir zu. Allerdings würde das Problem mit dem undefinierten Session-Zustand wegfallen, wenn ich direkt am Ende (aber noch innerhalb) der transaktions-dekorierten Funktion das Template erzeuge und fülle - an der Stelle, wo der Transaktionsdekorator noch greift. Dann sollte die Session einen gültigen Zustand haben und Fehler abgefangen werden, oder?
Ob ich nun
Code: Alles auswählen
@route('/bla/foo/route')
@view('bla/foo/template')
def bla_foo():
doing_foo()
return dict(foo=data)
oder
Code: Alles auswählen
@route('/bla/foo/route')
def bla_foo():
doing_foo()
return template('bla/foo/template', foo=data)
schreibe, macht imho kaum einen Unterschied. Somit wäre die Funktion nur noch mit route dekoriert und ich kann meinen Transaktionsdekorator dazusetzen.
Ich sträube mich irgendwie davor, noch mehr Bibliotheken (z.B. die von dir vorgeschlagene Middleware) einzubinden, um das Problem zu lösen. Naja, ich bilde mir ein, dass das Problem auch ohne Einsatz zusätzlicher Middleware lösbar sein sollte
LG Glocke
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Mittwoch 29. August 2012, 23:58
von deets
Wenn das so einfach geht, dann versuch's mit den Templates mal so. Aber sei dir *sicher*, dass da nicht ein generator zurueckgegeben wird. Ist unwahrscheinlich, aber better safe than sorry.
Und eine Middleware ist zb das hier:
Code: Alles auswählen
def wrapper(downstream_app):
def app(environ, start_response):
print "ick bin eene middlewaahre, wa"
return downstream_app(environ, start_response)
return app
Nicht gerade Raketenwissenschaft fuer die Problemloesung..

Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 00:13
von glocke
deets hat geschrieben:Code: Alles auswählen
def wrapper(downstream_app):
def app(environ, start_response):
print "ick bin eene middlewaahre, wa"
return downstream_app(environ, start_response)
return app
Nicht gerade Raketenwissenschaft fuer die Problemloesung..

Btw hab ich den Transaktionsdekorator, die Änderung bzgl. des Templatings implementiert und eine Kleinigkeit im "tiefen" Sourcecode des Spiels geändert (totale Dummheit die wahrscheinlich für die Verzögerung beim Laden und somit das entstehen des Lock-Wait-Timeouts gesorgt hat - boah das tat weh als ich die Stelle gesehen hab ^^ ). Jetzt kann ich den Fehler zumindest nicht mehr reproduzieren. Mal schauen *rumprobier*
LG Glocke
/EDIT: hier mal mein Transaktions-ExceptionLogging-Dekorator:
Code: Alles auswählen
def managed_transaction(func):
def _managed_transaction(*args, **kw):
try:
res = func(*args, **kw)
session.commit()
except Exception as e:
if isinstance(e, HTTPResponse):
session.commit()
raise
session.rollback()
syslog.log(e)
redirect(u'/internal_error/')
return res
return _managed_transaction
Hinweis: Im Falle einer HTTPResponse-"Exception" (verwendet von bottle für redirect() ) sollte ich besser auch die session commiten, bevor ich den Response raise (und die Umleitung fortführe). syslog ist nen selbstgeschriebener Logger - werde ich später durch das logging-Modul ersetzen.
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 08:01
von deets
Auch da wieder - ne Middleware fuer das Exception-Handling waere besser. Du mischst jetzt Transaktions-Handling mit spezifischem Error-handling.
Besser, diese beiden Aspekte zu trennen. WSGI erlaubt genau das.
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 09:37
von glocke
deets hat geschrieben:Auch da wieder - ne Middleware fuer das Exception-Handling waere besser. Du mischst jetzt Transaktions-Handling mit spezifischem Error-handling.
Besser, diese beiden Aspekte zu trennen. WSGI erlaubt genau das.
Naja aber ich muss die Fehler eh einmal abfangen um den Rollback zu machen. Meinst du ich soll den reraisen und dann von der Middleware (die ich um die bottle app lege, right?) das eigentliche Abfangen und loggen machen lassen?
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 09:51
von deets
Ja, lieber re-raisen. Du kannst dann zB sowas wie weberror um die App legen, welches dir gleich ein debugger in den browser rendert, oder fuer Produktionsbetrieb mails verschickt und so.
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 10:05
von glocke
Naja gut aber wo ist der Vorteil, wenn ich das nicht vom Dekorator mit machen lasse?
Code: Alles auswählen
from bottle import app, get, run, HTTPResponse
def exception_wrapper(app):
def _exception_wrapper(envir, start_response):
try:
return app(envir, start_response)
except HTTPResponse:
raise
except Exception as e:
print 'logged :{0}'.format(e)
return _exception_wrapper
def managed_transaction(func):
def _managed_transaction(*args, **kw):
try:
res = func(*args, **kw)
print 'session.commit'
return res
except HTTPResponse:
print 'session.commit also at redirect'
raise
except Exception as e:
print 'session.rollback'
raise
return _managed_transaction
app = app()
@app.route('/')
@managed_transaction
def index():
raise ValueError('Foo bar')
# apply middleware
app = exception_wrapper(app)
run(app, host=u'127.0.0.1', port=8080)
Würde das dann so ablaufen? Klappt nämlich nicht
LG Glocke
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 10:20
von deets
Dir fehlt ein
Und dann kommt's natuerlich zu Folgefehlern, weil dein Exception-Handling keine propere WSGI-App ist.
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 10:29
von glocke
Okay das bringt mich schonmal weiter. Dann bekomme ich allerdings
Code: Alles auswählen
Traceback (most recent call last):
File "/usr/lib/python2.7/wsgiref/handlers.py", line 86, in run
self.finish_response()
File "/usr/lib/python2.7/wsgiref/handlers.py", line 126, in finish_response
for data in self.result:
TypeError: 'NoneType' object is not iterable
weil ich im exception_wrapper (nach dem Loggen) nix zurückgebe. Ohne Fehler wird die gecallte app zurückgegeben. Was geb ich im Fehlerfall stattdessen zurück?
Naja dann stellt sich mir immernoch die Frage: Warum Wrapper UND Dekorator
LG Glocke

Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 12:37
von deets
Du musst natuerlich das WSGI-Protokoll sprechen. Also start_response mit einem redirect-header aufrufen, und ein leeres Iterable zurueckgeben zB.
Alllerdings gebe ich zu, dass ist vielleicht alles etwas viel des guten ist. Bzw. vielleicht nahelegt, ein Framework wie Django zu benutzen, wo an sowas alles schon gedacht ist. Oder eben weberror benutzen.
Alternativ koenntest du deine Awendung anders aufbauen, und die Transaktions-Ebene *unterhalb* der Actions machen, indem du deinen Code, der die eigentliche Anwendung darstellst, kapselst + mit Transaktionsmanagement ausruestest.
Und warum die Trennug? Ganz einfach: es sind zwei verschiedene Dinge, und sie zu vermengen fuehrt jetzt schon zu ziemlichem Spaghetti. Und Transaktionsmanagement ist kritisch, das will man klar trennen. Und wenn du zB Hintergrundprozesse hast, die auch die DB bearbeiten - dann hast du da zwar ne Transaktion und profitierst von dem Dekorator, aber kein HTTP involviert.
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 12:57
von glocke
deets hat geschrieben:Alllerdings gebe ich zu, dass ist vielleicht alles etwas viel des guten ist.
Deswegen frag ich nach dem Sinn der Trennung.
deets hat geschrieben:Und warum die Trennug? Ganz einfach: es sind zwei verschiedene Dinge, und sie zu vermengen fuehrt jetzt schon zu ziemlichem Spaghetti. Und Transaktionsmanagement ist kritisch, das will man klar trennen. Und wenn du zB Hintergrundprozesse hast, die auch die DB bearbeiten - dann hast du da zwar ne Transaktion und profitierst von dem Dekorator, aber kein HTTP involviert.
Naja einer der Hintergrundprozesse verwaltet die Kampfberechnungen. Dabei hab' ich die calculate-Methode in einen Try-Except-Block gesetzt. Dort werden die Fehler abgefangen, geloggt und die Transaktion zurückgerollt - oder committet, wenn kein Fehler auftrat.
Von daher würde ich meinen "Hybrid-Dekorator" so lassen. Ich finde es wäre etwas viel des Gute
Danke!
LG Glocke
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 13:22
von deets
Jo, und schon hast du zweimal nen Transaktions-Handlings-Code geschrieben.
Das *mindeste* was du tun solltest ist einen Transaktions-Dekorator schreiben, und den dann *innerhalb* deiner deines Fehlerbehandlungs-Dekorators seinerseits zu verwenden, um die uebergebene Funktion zu dekorieren. Dann hast du den Code nur einmal.
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 13:45
von glocke
Naja bei der Kampfberechnung auftretende Fehler sollen in einer anderen Log-File gespeichert werden. Sicherlich könnte ich einem Exception-Handling-Dekorator noch einen Parameter mitgeben. Dann müsste ich bei den 284 Routen jeweils einen Parameter im Exception-Handling-Dekorator mitgeben.
Entspricht das dem was du meinst?
Code: Alles auswählen
def managed_transaction(func):
def handle(*args, **kw):
try:
res = func(*args, **kw)
print 'session.commit'
return res
except HTTPResponse:
print 'session.commit also at redirect'
raise
except Exception as e:
print 'session.rollback'
raise
return handle
def errorhandle(logger):
def wrapper(func):
def handling(*args, **kw):
managed_func = managed_transaction(func)
try:
managed_func(*args, **kw)
except HTTPResponse:
raise
except Exception as e:
print 'logged "{0}" via {1}'.format(e, logger)
return handling
return wrapper
@errorhandle(logger='foo')
def bar():
raise Exception('Test')
# ...
# inside CombatManagement
for combat in Combat.query.filter(...).all():
combat.calculate()
# ...
# inside Combat class
@errorhandle(logger='bar')
def calculate(self):
# ...
LG Glocke
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 13:59
von deets
Ah, das mit der redirect-exception macht das auch haesslich. Naja, dann ist es wohl nicht wirklich moeglich das zu vereinigen.
Re: [Bottle] Clean-Up nach Broken Pipe
Verfasst: Donnerstag 30. August 2012, 14:13
von glocke
Ich lass die Sache einfach wie sie ist ^^