Python in Windows Shell - Destructor/Cleanup Problem...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
martin.chatterjee
User
Beiträge: 3
Registriert: Mittwoch 12. Mai 2010, 10:37

Moin zusammen,

ich kaempfe jetzt schon eine ganze Weile mit einem Problem - vielleicht kann mir einer von Euch dabei weiterhelfen:

Ich lasse ein Python Skript in einer Windows Shell laufen und moechte mitbekommen, wenn der User die Shell schliesst, um dann noch einiges an Cleanup auszufuehren (Datenbank-Connection schliessen, ...)

Wenn Der User Ctrl-C drueckt, klappt das alles wunderbar, aber wenn er einfach die Shell schliesst, indem der auf das Close-Icon clickt, beginnen meine Probleme.

Kurz mein prinzipieller Ansatz bis jetzt: ich hole mir ein Objekt meiner Klasse und rufe dann meine HauptMethode:

Code: Alles auswählen

  myObject = MyClass()
  myObject.mainLoopForever()
in __init__() wird eine Datenbank-Connection etabliert.
in mainLoopForever() werden alle paar Sekunden Daten mit der Datenbank abgeglichen.


Folgendes habe ich bereits ausprobiert: (klappt alles bei "Ctrl-C" und versagt alles beim Schliessen der Shell)

- __del__ implementieren und dort meinen Cleanup ausfuehren
- einen TRY FINALLY block in mainLoopForever() einbauen und dort meinen Cleanup ausfuehren
- __enter__ und __exit__ implementieren und mein Objekt mit dem WITH statement bauen


Bin dankbar fuer jede Hilfe und jedes Feedback... ;-)

Vielen Dank im voraus,

Martin
Benutzeravatar
jbs
User
Beiträge: 953
Registriert: Mittwoch 24. Juni 2009, 13:13
Wohnort: Postdam

Wenn du die shell schließt, dann beendest du Python auf eine eher unfreundliche Weise. Wie sehen denn deine Cleanups aus?

Und __del__ sollte man eher nicht verwenden.
[url=http://wiki.python-forum.de/PEP%208%20%28%C3%9Cbersetzung%29]PEP 8[/url] - Quak!
[url=http://tutorial.pocoo.org/index.html]Tutorial in Deutsch[/url]
martin.chatterjee
User
Beiträge: 3
Registriert: Mittwoch 12. Mai 2010, 10:37

jbs hat geschrieben:Wenn du die shell schließt, dann beendest du Python auf eine eher unfreundliche Weise.
Ich weiss, aber solange dieses Tool in einer sichtbaren Shell laeuft, muss ich mich mit dem Szenario befassen, dass der User einfach die Shell schliesst...
jbs hat geschrieben:Wie sehen denn deine Cleanups aus?
Meine Klasse hat eine Methode cleanup(), in der ein, zwei FileHandles geschlossen werden, der MaschinenStatus in einer verbundenen MySQL Datenbank auf "OFFLINE" gesetzt wird und schliesslich die Verbindung zur Datenbank geschlossen wird.

Unterm Strich ist mein Problem, dass diese Methode cleanup() zuverlaessig gerufen werden sollte, auch wenn der User die Shell einfach schliesst.

Die FileHandles und die DB-Connection "regeln" sich ja irgendwie von selbst (auch wenn es unsauber ist, sich nicht explizit darum zu kuemmern), aber der Status der Maschine in der Datenbank wird momentan nicht auf OFFLINE gesetzt...
jbs hat geschrieben:Und __del__ sollte man eher nicht verwenden.
Ja, da hast Du recht - hab ich eher der Vollstaendigkeit halber aufgelistet.

Unterm Strich "riecht" das eher danach, das ich grundsaetzlich an die Situation anders rangehen muss... ?

Martin
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Das von Dir gewünschte Verhalten erreichst Du, indem Du der Konsole einen Kontrollhandler mit der WinAPI-Funktion SetConsoleCtrlHandler zuweisst, einfaches Beispiel:

Code: Alles auswählen

from ctypes import CFUNCTYPE, windll
from ctypes.wintypes import BOOL, DWORD
from threading import Event
from time import sleep

cleanup_called = Event()

def cleanup():
    cleanup_called.set()
    print 'cleanup called'
    sleep(3)

def HandlerRoutine(dwCtrlType):
    '''http://msdn.microsoft.com/en-us/library/ms683242.aspx'''
    if dwCtrlType == 2:
        cleanup()
    return 0

HANDLERFUNC = CFUNCTYPE(BOOL, DWORD)
callback = HANDLERFUNC(HandlerRoutine)
windll.kernel32.SetConsoleCtrlHandler(callback, True)
count = 0
while not cleanup_called.isSet():
    print count
    count += 1
    sleep(1)
# do cleanup
sleep(3)
Dabei gibt es allerdings ein paar Dinge zu beachten:
Die callback-Funktion (im Bsp. HandlerRoutine) läuft in einem eigenen Thread, ergo darfst Du hier nur threadsichere Aufräumaktionen vornehmen. Ich weiß leider nicht, wie ctypes den Funktionscallback erstellt und inwieweit Pythons threading-Abstraktion (mit GIL usw.) mit der API-Funktion kollidieren könnte und würde nicht mit Locking usw. im cleanup-Handler rumspielen, sondern dem Hauptprogramm das close-Ereignis unterschieben und dort aufräumen lassen. Zumindest scheint Event() aus threading wie erwartet zu funktionieren. Dafür ist es wiederum nötig, dem Hauptprogramm genügend Zeit für den Cleanup einzuräumen, d.h. die callback-Funktion muß solange warten, bis das Hauptprogramm fertig ist (der Prozess wird ansonsten einfach von Windows gekillt und die Konsole geschlossen). Für eine echte Anwendung könntest Du z.B. ein zweites Event nach Abschluss der Aufräumaktion an den wartenden callback senden, der daraufhin beendet wird.

Der gesamte Aufräumspass sollte auch nicht zu lange dauern, sonst quittiert Windows den SchliessenButton-Klick mit einer Warnmeldung.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Hier nochmal eine etwas aufgeräumtere threadsichere Version:

Code: Alles auswählen

from ctypes import CFUNCTYPE, windll
from ctypes.wintypes import BOOL, DWORD
from threading import Event
from time import sleep

def console_ctrl_handler(event):
    '''Installs a general purpose console control handler, see
       _http://msdn.microsoft.com/en-us/library/ms686016.aspx
       event is a threading.Event() set by the handler,
       when any of Windows exit events in HandlerRoutine occurs.
       Use it to handle cleanup and clear the event afterwards.'''
    def HandlerRoutine(dwCtrlType):
        '''http://msdn.microsoft.com/en-us/library/ms683242.aspx'''
        event.set()
        while event.isSet():
            sleep(.1)
        return 0
    globals()['c_ctrl_cb'] = CFUNCTYPE(BOOL, DWORD)(HandlerRoutine)
    return windll.kernel32.SetConsoleCtrlHandler(globals()['c_ctrl_cb'], True)

if __name__ == '__main__':
    exit = Event()
    console_ctrl_handler(exit)
    count = 0
    while not exit.isSet():
        print count
        count += 1
        sleep(1)
    # for thread safety do cleanup here, not in the control handler
    print 'running cleanup...'
    sleep(3) # cleanup stuff
    exit.clear()
Der Kontrollhandler behandelt alle exit-Events, also auch Ctrl-C. Falls Du das ändern willst, müsstest Du auf den Wert von `dwCtrlType` in HandlerRoutine testen.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Noch was zu Deinem Grundproblem:

Einen zuverlässigen Aufruf Deines cleanups gibt es auch mit einem Kontrollhandler nicht. Dieser wird z.B. auch aufgerufen, wenn sich der User ausloggt, Services beendet werden etc. Zu diesem Zeitpunkt kann Windows nicht garantieren, das wichtige andere Dienste nicht schon beendet wurden und der Kontrollhandler eine degradierte Umgebung vorfindet. (Davor warnt die MSDN-Dokumentation auch ausdrücklich.)
Ebenso könnte ja auch die Netzwerkverbindung im laufenden Programm zur DB fehlschlagen und so einen ungültigen Zustand hinterlassen.

Diesem Problem lässt sich nur designtechnisch begegnen, z.B. in dem Du Transaktionen in Dein Zustandsmodell einführst und im Zweifelsfalle so eine Art Rollback auf den letzten gültigen Zustand ermöglichst.

Mit ein paar mehr Infos könnten wir Dir hier sicherlich weiterhelfen.

Grüsse jerch
martin.chatterjee
User
Beiträge: 3
Registriert: Mittwoch 12. Mai 2010, 10:37

jerch,

vielen Dank fuer Deine Hilfe.

War die letzten Tage auf einer anderen "Baustelle" gebunden, und kann mich jetzt wieder mit der Sache befassen.

Mein Bauchgefuehl geht mittlerweile auf jeden Fall auch in die Richtung, dass ich mein Problem durch eine grundsaetzliche Aenderung meiner Rangehensweise loesen sollte...

Auf jeden Fall haben mir Dein Feedback und Deine Kommentare schonmal sehr weitergeholfen!

GrussVomRhein,

Martin
Antworten