Seite wählen

NETWAYS Blog

atexit, oder wie man Python-Dienste nicht beenden sollte

Wer schon einmal einen Dienst mit Python realisiert hat, wird bereits vor der Aufgabe gestanden haben die Aufgaben die er verrichtet sauber und geordnet zu beenden. Python bietet einem hier vielerlei Lösungen an, darunter auch das atexit Modul. Für schnelle und simple Aufräum-Arbeiten ist dieses Modul wunderbar geeignet, nicht jedoch wenn Thread-Synchronisation und externe Kommunikation im Spiel ist. Warum das so ist und was die saubere Alternative ist, darum geht es heute in diesem Blogpost.
Wie der Name des Moduls und dessen Dokumentation bereits sagt, werden registrierte Routinen ausgeführt kurz bevor der Interpreter angehalten wird. Klingt erst einmal nicht besonders problematisch, ist es doch gerade was man haben möchte. Und es funktioniert sogar, solange keine Threads laufen. Dann werden die registrierten Routinen nicht aufgerufen. Das liegt daran, dass „normale“ Threads explizit beendet werden müssen, sonst verhindern diese den Stopp des Interpreters. Damit das nicht passiert, kommt man möglicherweise auf folgende Idee:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
t = threading.Thread(target=func)
t.daemon = True # Avoids that python hangs during shutdown
t.start()
[/sourcecode]
Ganz schlecht. Das mag möglicherweise den gewünschten Effekt haben und die Aufräum-Routinen können ihre Arbeit verrichten. Aber tatsächlich ist dies nur eine Lösung für ein Symptom, das eigentliche Problem ist noch immer nicht gelöst. Das wird einem spätestens klar, wenn es nicht mehr nur darum geht Threads zu beenden, sondern auch noch mit anderen über das Netzwerk verbundenen Diensten/Klienten eine saubere Unterbrechung der Kommunikation einzuleiten.
Denn was genau passiert wenn der Interpreter angehalten wird?

  1. Der MainThread wird sofort angehalten
  2. Offene File-Handles werden geschlossen
  3. Es wird gewartet dass alle nicht-Daemon Threads beendet wurden
  4. Die atexit-Routinen werden ausgeführt
  5. Garbage Collection
  6. Der Prozess wird beendet

Der zweite Punkt lässt einige vielleicht bereits aufhorchen, denn sockets sind nichts anderes als File-Handles. Wer nun immer noch denkt er könne in einem daemon-Thread die Netzwerk-Kommunikation mit seinem Gegenüber sauber beenden, ist auf dem Holzweg. Zum Zeitpunkt zu dem die atexit-Routinen laufen, sind bereits alle sockets unwiderruflich geschlossen.
Angenommen der Stopp des Dienstes wird ganz normal über SIGTERM initiiert, so könnte man nun alle atexit-Routinen in einen Signal-Handler verlagern. Dadurch würden sie laufen noch bevor der Interpreter angehalten wird, allerdings kommt man so sehr schnell wieder in die Bredouille, je nachdem welche Art von Arbeit der MainThread verrichtet. Denn da Signal-Handler asynchron im MainThread ausgeführt werden, ist das Risiko für ein Deadlock sehr groß. Kommen wir also zur erwähnten, sauberen Alternative: Feuer mit Feuer bekämpfen.
Was vormals in etwa so aussah:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
class SomeDaemon:
def start():
# …
signal.signal(signal.SIGTERM, self._sigterm)
atexit.register(self._atexit)
# …
def _sigterm(self, signum, frame):
sys.exit(0)
def _atexit(self):
# Alle Aufräum-Arbeiten
[/sourcecode]
Kann ganz einfach, mit durchschlagendem Erfolg, so umgebaut werden:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
class SomeDaemon:
def start():
# …
signal.signal(signal.SIGTERM, self._sigterm)
atexit.register(self._atexit)
# …
def _sigterm(self, signum, frame):
threading.Thread(target=self._cleanup, name=’CleanupThread‘).start()
def _cleanup(self):
# Komplexe Aufräum-Arbeiten
def _atexit(self):
# Einfache Aufräum-Arbeiten
[/sourcecode]
Der „CleanupThread“ sollte selbstverständlich keine Arbeit verrichten die unkontrolliert blockt. Denn dann sieht das ganze am Ende wieder so aus:
[sourcecode language=“python“ wraplines=“false“ collapse=“false“]
def _sigterm(self, signum, frame):
t = threading.Thread(target=self._cleanup, name=’CleanupThread‘)
t.daemon = True
t.start()
[/sourcecode]
Und der ganze Spaß geht von vorne los..

Johannes Meyer
Johannes Meyer
Lead Developer

Johannes ist seit 2011 bei uns und inzwischen, seit er 2014 die Ausbildung abgeschlossen hat, als Lead Developer für Icinga Web 2, Icinga DB Web sowie alle möglichen anderen Module und Bibliotheken im Web Bereich zuständig. Arbeitet er gerade mal nicht, macht er es sich bei schlechtem Wetter am liebsten zum zocken oder Filme/Serien schauen auf dem Sofa gemütlich. Passt das Wetter, geht's auch mal auf eines seiner Zweiräder. Motorisiert oder nicht.

Ubuntu – alles sauber!

Wer sein Benutzerprofil bei jeder Neuinstallation oder einem Update mitnimmt häuft schnell einige Gigabyte an Daten an. Cachedateien von Browsern oder Emailanwendungen wachsen sehr schnell und unkontrolliert. Weiterhin hinterlassen alte Installation ihre Daten und Konfigurationen. Schenkt man ihnen keine Beachtung erhält man ein beachtliches Datengrab. Es gibt auch einen entscheidenden Nachteil beim Aktualisieren der Ubuntu Distribution: Neue Features bleiben aus! Durch die Anpassung von Autostartprogrammen und den Gnome Panels tauchen neue Gadgets von Ubuntu gar nicht erst auf – Ob man sie nun braucht oder nicht.
Auf jeden Fall: Starten mit einem neuen Profil. Natürlich nicht ohne eine Sicherung. Wichtige Programme wie Firefox oder Thunderbird kann man im Profil belassen. Vermisst man bestimmte Dinge oder Konfigurationen einer Anwendung lassen sich diese durch das bestehende Backup leicht wiederherstellen.
Der Hauptgrund für eine verhunzte Konfiguration liegt in den Verzeichnissen $HOME/.gnome2 und $HOME/.config. Ein Reset dieser Verzeichnisse biegt die meisten Fehler wieder gerade.
Gegen Datenhalden gibt es einige Programme die helfen: Ubuntu Tweak, gconf-cleaner oder bleachbit räumen mehr oder weniger stark auf.
Ubuntu Tweak: Entfernt Apt Cache Dateien und findet globale Konfigurationen von nicht mehr installierten Anwendungen. Auch bietet dieses Stück Software einige weitere interessanter Features die allerdings den Ramen sprengen würden.
GConf Cleaner: Räumt die Gnome Registrierung auf, findet alte Einträge im Home aus dem Gconf Backend, sichert diese bei bedarf und lädt die Gnome Konfiguration neu.
BleachBit: Räumt Programmspezifische Einzelteile auf, z.B. Bash History, Chromium Cache und findet auch Dateien welche von Windows- und Macbenutzern hinterlassen worden sind. Insgesamt sehr umfangreich, dafür erhält man am Ende den meisten Speicherplatz!


Alle Programme gibt es über aptitude, außer Ubuntu Tweak welches über Launchpad zu Verfügung gestellt wird! Da sich bei vielen Distributionen schon vieles im verborgenen abspielt wird man überrascht sein was sich selbst in kurzer Zeit alles ansammelt.
Allen eine saubere Woche 😛

Marius Hein
Marius Hein
Head of IT Service Management

Marius Hein ist schon seit 2003 bei NETWAYS. Er hat hier seine Ausbildung zum Fachinformatiker absolviert und viele Jahre in der Softwareentwicklung gearbeitet. Mittlerweile ist er Herr über die interne IT und als Leiter von ITSM zuständig für die technische Schnittmenge der Abteilungen der NETWAYS Gruppe. Wenn er nicht gerade IPv6 IPSec Tunnel bohrt, sitzt er daheim am Schlagzeug und treibt seine Nachbarn in den Wahnsinn.