Asynchrones Progrämmchen dämonisieren

  • Ich habe ein kleines Programm geschrieben, das ein Verzeichnis auf hineingeschobene Dateien überwacht, diese verarbeitet und anschließend löscht. Läuft soweit gut. Jetzt suche ich nach einer Möglichkeit, dieses nach dem Start in den Hintergrund zu schieben. Ich habe das mit threading.Thread(... , daemon=True) sowie mit daemonize probiert. In beiden Fällen beendet sich das Programm einfach.


    Gibt es ein prinzipielles Problem mit asyncio und Hintergrundtasks? Kennt jemand eine Methode?


    Reduzierter Quellcode ohne Dämonisierungsversuch:

    (sämtliche Funktionalität durch prints ersetzt, die bei meinen Dämonisierungsversuchen auskommentiert waren.

  • Ist "nach dem start" wichtig, oder geht es auch "beim Start"? Ansonsten ware ein "&" in der SHell die richtige wahl. Wenn das Programm auf die Konsole Schreibt, entwede den stdout nach /dev/null umleiten, oder eine screen-session starten. ("screen -d -m")

  • Wäre natürlich (mit einem zusätzlichen nohup o.ä.) eine Option, aber es würde mich wurmen, wenn ich das nicht nativ in Python hinbekäme.

  • Hallo,


    ich verstehe den Ansatz nicht, warum du das unbedingt mit Python lösen willst - das ist IMHO der falsche Ansatz. Und der Ansatz mittels threading macht IMHO so wie so keinen Sinn.


    Richtig wäre, das Skripte über eine systemd Service Unit zu starten. dann läuft das Skript wie ein Dienst automatisch im Hintergrund.


    Gruß, noisefloor

  • Das Skript soll auf einem älteren System zum Einsatz kommen, auf dem noch kein systemd läuft. Außerdem geht es mir mittlerweile auch darum, prinzipiell rauszukriegen, wie das geht. Ob ich's dann tatsächlich einsetze, ist eine andere Frage.

    Und der Ansatz mittels threading macht IMHO so wie so keinen Sinn.

    Magst Du erläutern warum?

  • Hallo,


    Quote


    Magst Du erläutern warum?

    Dein Programm läuft im loop von asyncio. Technisch gesehen ist das ein Thread, der die Kontrolle hat. Wenn du jetzt den loop in einen eigenen Thread auslagerst, dann hast du einfach nur _einen_ Thread, der _einen_ anderen Thread startet - und das macht keinen Sinn. Du baust einfach nur eine Ebene mehr ein, die nichts bringt.


    Wenn du kein systemd mit systemd hast, dann starte das Skript halt über Upstart oder SysVInit oder was auch immer. Oder du nutzt ein Tool wie supervisord (ist auch in Python geschrieben ;-) ), welches Prozesse im Hintergrund startet und überwacht.


    Gruß, noisefloor

  • Ich habe das Problem gefunden: Es war der asyncio-eventloop, auf den der Hintergrundprozess nicht mehr zugreifen konnte. So geht's (mit python-daemon):



    Danke, wend und noisefloor , fürs Mitdenken!

  • Hallo,


    Anmerkung noch zum Code: so ganz stilecht ist das nicht, weil die Funktion `stager` nicht wirklich asynchron ist.

    Grund: damit eine Funktion im Sinne von asyncio asynchron ist, muss diese "awaitable" sein. Heißt, dass die Funktion unterbrochen werden kann, wenn diese auf etwas wartet. `stager` "wartet" aber nur auf das `async.sleep(0)` - was aber kein wirkliches warten ist, sondern eher ein Hack, damit sich asyncio nicht beschwert, dass die Funktion nicht awaitable ist.


    Gruß, noisefloor

  • Leuchtet mir nicht ganz ein. Nach meinem Verständnis ist asyncio eine Möglichkeit, ohne Threading oder gar Spawnen neuer Prozesse Nebenläufigkeit zu erreichen. Voraussetzung dafür ist, daß alle "gleichzeitig" laufenden Tasks fair spielen und nicht blockieren, sondern sich an irgendeiner Stelle unterbrechen lassen. Und genau das macht mein stager: Er hat eine Liste abzuarbeiten und nach jedem Eintrag gibt er die Kontrolle kurz an den loop zurück.


    Wenn ich das Konzept falsch verstanden haben sollte, bin ich für Aufklärung dankbar.

  • Servus Manul ,

    ich kann jetzt nicht beurteilen, inwieweit Dir das weiterhilft.

    Aber in "C" ist asynchroner I/O so implementiert, dass bei einem I/O Ereignis entweder ein (vorher "installierter") Handler (Stickwort Eventhandler) aufgerufen oder ein entsprechendes Signal an "main" geschickt wird, das dann darauf reagieren muss.


    cu,

    -ds-

  • Hallo,


    Manul : hast du schon richtig verstanden. Wenn eine Funktion A, die im asyncio-Loop auf etwas wartet, dann geht der Loop zur nächsten Funktion B und arbeitet an dieser, bis diese auf etwas wartet, dann ist A wieder dran usw.


    Nur bei dir dauert das Warten genau null (0) Sekunden, dass heißt `stager` ist direkt wieder dran. Formell ist es so richtig für asyncio programmiert, weil `stager` awaitable ist, praktisch ist es, wie oben beschrieben, pseudo-asynchron.


    Was das pyinotify-Modul macht kann ich nicht sagen, habe ich noch nie benutzt.


    Gruß, noisefloor

  • Okay, vielleicht ist das ein Mißverständnis: stager ist nur dann sofort wieder dran, wenn es keine anderen Tasks gibt. pyinotify ruft aber für jede Datei, die ins überwachte Verzeichnis geschoben wird, die Funktion stager auf, so daß durchaus mehrere dieser Funktionsaufrufe gleichzeitig ablaufen und jeweils ihre "eigene" Datei bearbeiten können.

  • Hallo,


    ok... trotzdem ist es ein Hack. Was bei `stager` der blockierende Teil ist, ist ja auch der I/O von der Festplatte - und der läuft synchron, weil mit Bordmitteln von Python. Wenn `with open(filename) as file:` 5 Sekunden dauert, dann blockiert dein Programm 5 Sekunden - da wird die Kontrolle nämlich nicht abgegeben. Es läuft nicht so asynchron, wie du denkst...

    asyncio, also das Python-Modul, unterstützt OOTB kein Festplatten I/O. Dafür gibt es zwei externe Module, aiofile und aiofiles (keine Ahnung, ob eines davon was taugt...), die Festplatten I/O für asyncion awaitable machen.

    ich würde hier auch ggf. einen anderen Ansatz wählen: der Callback von pyinotify schiebt den Dateinamen einfach nur in eine Queue. Das geht schnell und blockiert wohl nicht. Die Queue wird ein einem parallel laufenden Thread oder Process abgearbeitet.


    Gruß, noisefloor

  • Es läuft nicht so asynchron, wie du denkst...

    Woher weißt Du, was ich denke? ;)


    Daß die einzelnen Operationen blockieren, ist mir durchaus bewusst, ich gehe aber davon aus, daß das keine sehr lange tut. Was ich im wesentlichen vermeiden wollte, ist, daß der stager beim Abarbeiten einer langen Liste blockiert, bis er die ganz durch hat. Ich würde vermuten, daß mein Ansatz hierfür gut genug ist, falls sich das als Irrtum herausstellt, mache ich mir weitere Gedanken und werde dann auch Deine Anregungen berücksichtigen – vielen Dank dafür!

  • Hallo,


    Quote


    Woher weißt Du, was ich denke?

    Weil meine Glaskugel ausnahmsweise mal funktioniert hat ;-)


    Quote


    daß der stager beim Abarbeiten einer langen Liste blockiert,

    Wenn du das zeilenweise einlesen der Datei und Bearbeitung der Zeilen meinst -> das blockiert. Das einzige, was dann evtl. warten muss, ist das `os.remove()`.


    Quote


    Ich würde vermuten, daß mein Ansatz hierfür gut genug ist,

    Das hängt halt davon ab, wie oft das Event von inotify. eintritt. Wenn's nicht gerade dutzende Mal pro Sekunde ist und dein Festplatte lahm ist, wird das wohl reichen - ich vermute mal, dass würde in dem Fall auch ohne asyncio reichen...


    Gruß, noisefloor

  • Wenn du das zeilenweise einlesen der Datei und Bearbeitung der Zeilen meinst -> das blockiert.

    Klar, aber nach jeder Zeile dürfen die anderen Tasks wieder mitspielen.

    ich vermute mal, dass würde in dem Fall auch ohne asyncio reichen

    Das kommt m.E. eher darauf an, wie viele Einträge die einzelnen Dateien beinhalten. Wenn das nur eine Hand voll sind, ist asyncio sicher überflüssig. Falls es aber in die Tausende gehen sollte, hätte ich Bedenken, daß das inotify sonst verhungert.