Warum wird mein Python-Hintergrundprozess beendet, wenn die SSH-Sitzung beendet wird?

19

Ich habe ein Bash-Skript, das ein Python3-Skript startet (nennen wir es startup.sh), mit der folgenden Schlüsselzeile:

nohup python3 -u <script> &

Wenn ich sshdirekt in dieses Skript einsteige und es aufrufe, läuft das Python-Skript nach dem Beenden im Hintergrund weiter. Wenn ich dies jedoch ausführe:

ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"

Der Vorgang endet, sobald sshdie Ausführung beendet ist und die Sitzung geschlossen wurde.

Was ist der Unterschied zwischen den beiden?

BEARBEITEN: Das Python-Skript führt einen Webdienst über Bottle aus.

EDIT2: Ich habe auch versucht , ein Init-Skript zu erstellen , das aufruft startup.shund ausgeführt wird ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "sudo service start <servicename>", aber das gleiche Verhalten hat.

EDIT3: Vielleicht ist es etwas anderes im Skript. Hier ist der Großteil des Skripts:

chmod 700 ${key_loc}

echo "INFO: Syncing files."
rsync -azP -e "ssh -i ${key_loc} -o StrictHostKeyChecking=no" ${source_client_loc} ${remote_user}@${remote_hostname}:${destination_client_loc}

echo "INFO: Running startup script."
ssh -i ${key_loc} -o StrictHostKeyChecking=no ${remote_user}@${remote_hostname} "cd ${destination_client_loc}; chmod u+x ${ctl_script}; ./${ctl_script} restart"

EDIT4: Wenn ich die letzte Zeile mit einem Schlaf am Ende laufen lasse:

ssh -i ${key_loc} -o StrictHostKeyChecking=no ${remote_user}@${remote_hostname} "cd ${destination_client_loc}; chmod u+x ${ctl_script}; ./${ctl_script} restart; sleep 1"

echo "Finished"

Es erreicht nie echo "Finished", und ich sehe die Flaschen-Server-Nachricht, die ich noch nie gesehen habe:

Bottle vx.x.x server starting up (using WSGIRefServer())...
Listening on <URL>
Hit Ctrl-C to quit.

Ich sehe "Fertig", wenn ich manuell SSH einsetze und den Vorgang selbst beende.

EDIT5: Wenn ich mit EDIT4 eine Anfrage an einen Endpunkt stelle, erhalte ich eine Seite zurück, aber die Flasche zeigt Fehler an:

Bottle vx.x.x server starting up (using WSGIRefServer())...
Listening on <URL>
Hit Ctrl-C to quit.


----------------------------------------
Exception happened during processing of request from ('<IP>', 55104)
neverendingqs
quelle
Gibt es eine Möglichkeit, die Funktionsweise des Python-Skripts genauer zu beschreiben? Sie würden wahrscheinlich immer noch nur Vermutungen ohne den vollständigen Quellcode erhalten, aber wenn Sie mehr über die Funktionsweise des Python-Skripts wissen, können Sie möglicherweise besser fundierte Vermutungen anstellen.
Bratchley
Ja - zur Frage hinzugefügt.
neverendingqs
Das Skript macht möglicherweise etwas Frühes, das irgendwie vom angeschlossenen Terminal abhängt, oder so, und es könnte ein Zeitproblem sein: Wenn die Sitzung nach den ersten Sekunden dauert, funktioniert es, sonst nicht. Die beste Option ist es, es unter straceLinux oder trussunter Solaris auszuführen und zu sehen, wie / warum es beendet wird. Wie zum Beispiel ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> strace -fo /tmp/debug ./startup.sh.
Celada
Haben Sie versucht, das &Skript am Ende des Startvorgangs zu verwenden? Durch Hinzufügen von &wird die Abhängigkeit Ihrer ssh-Sitzung von der übergeordneten ID aufgehoben (wenn übergeordnete IDs sterben, werden auch deren untergeordnete IDs gelöscht). Ich denke auch, dass dies eine doppelte Frage ist, die auf diesem vorherigen Beitrag basiert . Der Beitrag, den ich Ihnen im vorherigen Satz übermittelt habe, ist ein Duplikat dieses Beitrags, das möglicherweise detailliertere Informationen enthält.
Jacob Bryan
Ich habe es schon versucht nohup ./startup.sh &, aber es hatte das gleiche Verhalten. startup.shEnthält bereits eine Gabel ( nohup python3 -u <script> &), daher bin ich mir ziemlich sicher, dass ich nicht noch einmal gabeln muss.
neverendingqs

Antworten:

11

Ich würde den Befehl von seiner Standard-Ein- / Ausgabe trennen und Fehlerflüsse:

nohup python3 -u <script> </dev/null >/dev/null 2>&1 &  

sshbenötigt einen Indikator, der keine Ausgabe mehr hat und der keine Eingabe mehr benötigt. Etwas anderes als die Eingabe zu haben und die Ausgabeeinrichtung umzuleiten, sshkann sicher beendet werden, da die Eingabe / Ausgabe nicht vom Terminal kommt oder zum Terminal geht. Dies bedeutet, dass die Eingabe von einer anderen Stelle stammen muss und die Ausgabe (sowohl STDOUT als auch STDERR) an eine andere Stelle gehen sollte.

Das </dev/nullTeil wird /dev/nullals Eingabe für angegeben <script>. Warum ist das hier sinnvoll:

Wenn Sie / dev / null nach stdin umleiten, erhält jeder Leseaufruf aus diesem Prozess eine sofortige EOF. Dies ist normalerweise nützlich, um einen Prozess von einem tty zu trennen (ein solcher Prozess wird als Daemon bezeichnet). Wenn Sie beispielsweise einen Hintergrundprozess remote über ssh starten, müssen Sie stdin umleiten, um zu verhindern, dass der Prozess auf lokale Eingaben wartet. /programming/19955260/what-is-dev-null-in-bash/19955475#19955475

Alternativ sollte das Umleiten von einer anderen Eingabequelle relativ sicher sein, solange die aktuelle sshSitzung nicht geöffnet bleiben muss.

Mit dem >/dev/nullTeil leitet die Shell die Standardausgabe nach / dev / null um und verwirft sie im Wesentlichen. >/path/to/filewird auch funktionieren.

Der letzte Teil 2>&1leitet STDERR zu STDOUT um.

Es gibt drei Standard-Eingabe- und Ausgabequellen für ein Programm. Die Standardeingabe erfolgt normalerweise über die Tastatur, wenn es sich um ein interaktives Programm handelt, oder über ein anderes Programm, wenn es die Ausgabe des anderen Programms verarbeitet. Das Programm druckt normalerweise auf Standardausgabe und manchmal auf Standardfehler. Diese drei Dateideskriptoren (Sie können sich diese als „Datenpipes“ vorstellen) werden häufig als STDIN, STDOUT und STDERR bezeichnet.

Manchmal sind sie nicht benannt, sondern nummeriert! Die für sie integrierten Nummerierungen sind 0, 1 und 2 in dieser Reihenfolge. Wenn Sie den Namen oder die Nummer eins nicht explizit angeben, sprechen Sie standardmäßig von STDOUT.

In diesem Kontext können Sie sehen, dass der obige Befehl die Standardausgabe nach / dev / null umleitet. Hier können Sie alles ablegen, was Sie nicht möchten (oft als Bit-Bucket bezeichnet) und dann den Standardfehler in die Standardausgabe umleiten ( Sie müssen ein & vor das Ziel stellen, wenn Sie dies tun.

Die kurze Erklärung lautet daher: "Alle Ausgaben dieses Befehls sollten in ein schwarzes Loch geschoben werden."
Was bedeutet> / dev / null 2> & 1? | Xaprb

jlliagre
quelle
nohup python3 -u <script> >/dev/null 2>&1 &und nohup python3 -u <script> > nohup.out 2>&1 &arbeitete. Ich dachte, nohup leitet die gesamte Ausgabe automatisch um - was ist der Unterschied?
neverendingqs
@neverendingqs, welche Version nohuphast du auf deinem Remote-Host? Ein POSIX nohupist nicht erforderlich, um umzuleiten stdin, was ich verpasst habe, aber es sollte trotzdem umleiten stdoutund stderr.
Graeme
Sieht so aus, als würde ich damit arbeiten nohup (GNU coreutils) 8.21.
neverendingqs
@neverendingqs, nohupwerden Nachrichten gedruckt, wie z nohup: ignoring input and appending output to ‘nohup.out’.
Graeme
Ja - das ist die genaue Botschaft.
neverendingqs
3

Schau dir an man ssh:

 ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
     [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-L [bind_address:]port:host:hostport]
     [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
     [-R [bind_address:]port:host:hostport] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
     [user@]hostname [command]

Wenn Sie ausführen, führen ssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> "./startup.sh"Sie das Shell-Skript startup.sh als ssh-Befehl aus.

Aus der Beschreibung:

Wenn ein Befehl angegeben wird, wird er auf dem Remote-Host anstelle einer Anmeldeshell ausgeführt.

Auf dieser Grundlage sollte das Skript remote ausgeführt werden.

Der Unterschied zwischen diesem und dem Ausführen nohup python3 -u <script> &in Ihrem lokalen Terminal besteht darin, dass dies als lokaler Hintergrundprozess ausgeführt wird, während der Befehl ssh versucht, ihn als entfernten Hintergrundprozess auszuführen.

Wenn Sie das Skript lokal ausführen möchten, führen Sie startup.sh nicht als Teil des Befehls ssh aus. Sie könnten so etwas versuchenssh -i <keyfile> -o StrictHostKeyChecking=no <user>@<hostname> && "./startup.sh"

Wenn Sie beabsichtigen, das Skript remote auszuführen und dieser Prozess fortgesetzt werden soll, nachdem Ihre SSH-Sitzung beendet wurde, müssen Sie zuerst eine screenSitzung auf dem Remote-Host starten . Dann müssen Sie das Python-Skript auf dem Bildschirm ausführen, und es wird weiterhin ausgeführt, nachdem Sie Ihre SSH-Sitzung beendet haben.

Siehe Bildschirm-Benutzerhandbuch

Obwohl ich denke, dass Bildschirm die beste Option ist, sollten Sie die Einstellung shopt -s huponexitauf dem Remote-Host in Betracht ziehen, bevor Sie den Befehl nohup ausführen, wenn Sie nohup verwenden müssen . Alternativ können Sie disown -h [jobID]den Prozess mit markieren, damit SIGHUP nicht an ihn gesendet wird. 1

Wie kann ich den Job weiter ausführen, nachdem ich eine Shell-Eingabeaufforderung im Hintergrund beendet habe?

Das Signal SIGHUP (Auflegen) wird von Ihrem System zur Steuerung des Terminals oder zum Beenden des Steuerungsprozesses verwendet. Mit SIGHUP können Sie Konfigurationsdateien neu laden und auch Protokolldateien öffnen / schließen. Mit anderen Worten, wenn Sie sich von Ihrem Terminal abmelden, werden alle laufenden Jobs beendet. Um dies zu vermeiden, können Sie die Option -h an disown übergeben. Diese Option markiert jede Job-ID, damit SIGHUP nicht an den Job gesendet wird, wenn die Shell ein SIGHUP empfängt.

Sehen Sie sich auch diese Zusammenfassung an, wie es huponexitfunktioniert, wenn eine Shell beendet, getötet oder fallengelassen wird. Ich vermute, dass Ihr aktuelles Problem damit zusammenhängt, wie die Shell-Sitzung endet. 2

  1. Alle untergeordneten Prozesse, die im Hintergrund oder nicht in einer Shell ausgeführt werden, die über eine SSH-Verbindung geöffnet wurde, werden mit SIGHUP beendet, wenn die SSH-Verbindung nur dann geschlossen wird, wenn die Option huponexit aktiviert ist: Führen Sie shopt huponexit aus, um festzustellen, ob dies zutrifft.

  2. Wenn huponexit true ist, können Sie nohup oder disown verwenden, um den Prozess von der Shell zu trennen, damit er beim Beenden nicht abgebrochen wird. Oder führen Sie die Dinge mit dem Bildschirm aus.

  3. Wenn huponexit false ist, was heutzutage zumindest unter einigen Linux-Betriebssystemen die Standardeinstellung ist, werden Jobs im Hintergrund beim normalen Abmelden nicht abgebrochen.

  4. Aber selbst wenn huponexit falsch ist, wird die SSH-Verbindung abgebrochen oder unterbrochen (anders als beim normalen Abmelden), und die Prozesse im Hintergrund werden trotzdem abgebrochen. Dies kann wie in (2) durch Disown oder No-Up vermieden werden.

Abschließend einige Beispiele zur Verwendung von shopt huponexit. 3

$ shopt -s huponexit; shopt | grep huponexit
huponexit       on
# Background jobs will be terminated with SIGHUP when shell exits

$ shopt -u huponexit; shopt | grep huponexit
huponexit       off
# Background jobs will NOT be terminated with SIGHUP when shell exits
Iyrin
quelle
Laut bashManpage huponexitsollte sich dies nur auf interaktive Shells und nicht auf Skripte auswirken - 'Wenn die Shell-Option huponexit mit shopt gesetzt wurde, sendet bash beim Beenden einer interaktiven Login-Shell ein SIGHUP an alle Jobs.'
Graeme
2

Vielleicht lohnt es sich, eine -nOption zu versuchen, wenn Sie eine starten ssh? Dadurch wird die Abhängigkeit von Remote-Prozessen von einem lokalen Prozess verhindert stdin, der natürlich sofort nach dem ssh sessionEnde geschlossen wird. Und dies führt zu einer Kündigung der Remote-Preise, wenn versucht wird, auf seine zuzugreifen stdin.

Georgiy
quelle
Versuchte es ohne Erfolg = [.
neverendingqs
2

Ich vermute, Sie haben einen Rennzustand. Es würde ungefähr so ​​aussehen:

  • Die SSH-Verbindung wird gestartet
  • SSH startet die Datei startup.sh
  • startup.sh startet einen Hintergrundprozess (nohup)
  • startup.sh wird beendet
  • ssh wird beendet, und dies beendet die untergeordneten Prozesse (dh nohup)

Wenn ssh die Dinge nicht gekürzt hätte, wäre Folgendes passiert (über die Reihenfolge dieser beiden nicht sicher):

  • nohup startet dein Python-Skript
  • nohup trennt sich vom übergeordneten Prozess und Terminal.

Die letzten beiden kritischen Schritte finden also nicht statt, da startup.sh und ssh beendet werden, bevor nohup Zeit hat, seine Sache zu erledigen.

Ich gehe davon aus, dass Ihr Problem verschwindet, wenn Sie am Ende von startup.sh einige Sekunden in den Ruhezustand versetzen. Ich weiß nicht genau, wie viel Zeit Sie brauchen. Wenn es wichtig ist, es auf ein Minimum zu beschränken, können Sie sich vielleicht etwas in proc ansehen, um zu sehen, wann es sicher ist.

mc0e
quelle
Gut, denken Sie nicht, dass das Fenster dafür sehr lang sein wird - wahrscheinlich nur ein paar Millisekunden. Sie könnten überprüfen, ob die Ausgabe von /proc/$!/commnicht nohupoder eher portabel ist ps -o comm= $!.
Graeme
Das sollte bei normaler Abmeldung funktionieren, aber wie sieht es aus, wenn die Sitzung unterbrochen oder beendet wird? Müssten Sie den Job nicht immer noch ablehnen, damit er durch Seufzen völlig ignoriert wird?
Iyrin
@RyanLoremIpsum: Das Startskript muss nur so lange warten, bis der untergeordnete Prozess vollständig getrennt ist. Danach ist es egal, was mit der ssh-Sitzung passiert. Wenn etwas anderes Ihre SSH-Sitzung in dem kurzen Fenster beendet, während dies geschieht, können Sie nicht viel dagegen tun.
mc0e
@Graeme Ja, ich nehme an, es ist sehr schnell, aber ich weiß einfach nicht genug darüber, was Nohup genau macht, um sicherzugehen. Ein Verweis auf eine maßgebliche (oder zumindest sachkundige und detaillierte) Quelle zu diesem Thema wäre hilfreich.
mc0e
Wie wäre es mit diesem - lingrok.org/xref/coreutils/src/nohup.c
Graeme
1

Dies klingt eher nach einem Problem mit dem, was das pythonSkript oder pythonselbst tut. Alles, was nohupwirklich funktioniert (Umleitungen vereinfachen), ist, den Handler für das HUPSignal auf SIG_IGN(Ignorieren) zu setzen, bevor das Programm ausgeführt wird. Es gibt nichts, was das Programm daran hindern könnte, es zurückzusetzen SIG_DFLoder einen eigenen Handler zu installieren, sobald es gestartet wird .

Möglicherweise möchten Sie Ihren Befehl in Klammern setzen, damit Sie einen doppelten Verzweigungseffekt erhalten und Ihr pythonSkript nicht länger ein untergeordnetes Element des Shell-Prozesses ist. Z.B:

( nohup python3 -u <script> & )

Eine andere Sache, die auch einen Versuch wert sein kann (wenn Sie bashund nicht eine andere Shell verwenden), ist, das disowneingebaute anstelle zu verwenden nohup. Wenn alles so funktioniert, wie es dokumentiert ist, sollte dies eigentlich keinen Unterschied machen. In einer interaktiven Shell würde dies jedoch verhindern, dass sich das HUPSignal auf Ihr pythonSkript ausbreitet . Sie können die Ablehnung in der nächsten Zeile oder in der gleichen Zeile wie unten hinzufügen (beachten Sie, dass das Hinzufügen von a ;nach a &ein Fehler in ist bash):

python3 -u <script> </dev/null &>/dev/null & disown

Wenn das oben Genannte oder eine Kombination davon nicht funktioniert, ist der einzige Ort, an dem das Problem behoben werden kann, sicherlich das pythonSkript selbst.

Graeme
quelle
Wäre der Doppelgabeleffekt ausreichend (basierend auf der Antwort von @ RyanLoremIpsum)?
neverendingqs
Beide haben das Problem nicht gelöst = [. Wenn es sich um ein Python-Problem handelt, haben Sie eine Idee, wo Sie mit der Untersuchung beginnen sollen (Sie können hier nicht zu viel des Python-Skripts posten)?
neverendingqs
@neverendingqs, wenn Sie das huponexitZeug meinen, sollte das Ausführen in einer Subshell den gleichen Effekt haben, disownda der Prozess nicht zur Jobliste hinzugefügt wird.
Graeme
@neverendingqs, hat meine Antwort aktualisiert. Ich habe vergessen, dass Sie Weiterleitungen mit verwenden sollten disown. Erwarten Sie jedoch nicht, dass es viel bewirken wird. Am besten ändern Sie das pythonSkript so, dass Sie wissen, warum es beendet wird.
Graeme
Das Umleiten der Ausgabe hat funktioniert ( unix.stackexchange.com/a/176610/52894 ), aber ich bin nicht sicher, was der Unterschied zwischen dem expliziten Ausführen und dem Ausführen nohupist.
neverendingqs
0

Ich denke, es liegt daran, dass der Job an die Sitzung gebunden ist. Sobald dies beendet ist, werden auch alle Benutzeraufträge beendet.

user208145
quelle
2
Aber warum ist das anders als ein Terminal zu bekommen, den Befehl einzugeben, auszuführen und zu beenden? Beide Sitzungen sind geschlossen, sobald ich sie schließe.
NeverendingQS
Stimmen Sie zu, ich würde gerne verstehen, warum dies nicht anders ist als das manuelle Schließen Ihres eigenen Terminals.
Avindra Goolcharan
0

Wenn Sie nohupdie Ausgabedatei öffnen können, haben Sie möglicherweise einen Hinweis darauf nohup.out. Es ist möglich, dass Sie pythonsich nicht im Pfad befinden, wenn Sie das Skript über ausführen ssh.

Ich würde versuchen, eine Protokolldatei für den Befehl zu erstellen. Versuchen Sie es mit:

nohup /usr/bin/python3 -u <script> &>logfile &
BillThor
quelle
Ich benutze ssh, um das Skript manuell auszuführen, also gehe ich davon aus, dass sich Python3 im Pfad befindet.
neverendingqs
@neverendingqs Enthält die Protokolldatei etwas?
BillThor
Nichts Ungewöhnliches - der Start sieht normal aus.
NeverendingQS