Seite 1 von 2

subprocess -> wann beendet?

Verfasst: Mittwoch 16. April 2008, 12:17
von droptix
Ich starte ein Kommando mittels:

Code: Alles auswählen

process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Ich möchte einen Timeout festlegen, um den Sub-Prozess ggf. automatisch zu beenden, wenn er z.B. nach 15 Sekunden nicht "antwortet". Wie aber kann ich erkennen, ob der Sub-Prozess fertig ist, also beendet wurde? Habe es mit dem `returncode` versucht, aber es gibt ja durchaus Kommandos (z.B. `dir` unter Windows oder `ls` unter Linux), die keinen Returnwert liefern. Dann gibt es da noch die `poll()` Funktion, aber die reagiert auch nicht unbedingt... die Dokumentation verschweigt da vllt. ein paar wichtige Details.

Kann mir wer unter die Arme greifen?

Verfasst: Mittwoch 16. April 2008, 12:27
von sma
Die von dir verlinkte Doku sagt doch, dass `returncode`ein `None` liefert, wenn der Prozess noch nicht beendet wurde oder ansonsten eben ein `int` mit dem Returncode. Das sollte doch zur Unterscheidung reichen, oder?

Stefan

Verfasst: Mittwoch 16. April 2008, 12:37
von BlackJack
@droptix: Und *jedes* Programm, das sich beendet, gibt einen Returncode zurück, inklusive ``dir`` und ``ls``.

Verfasst: Mittwoch 16. April 2008, 12:42
von Rebecca
Jeder Prozess gibt einen Returncode zurueck. Bei shell=True bekommst du den returncode von der Shell.

Mmh, bei mir steht in returncode erst was drin, wenn ich poll aufgerufen habe:

Code: Alles auswählen

>>> p = subprocess.Popen("/bin/ls")
einfuehrung.pdf  einfuehrung.tex
>>> p.returncode
>>> p.returncode
>>> p.returncode
>>> p.returncode
>>> p.returncode
>>> p.poll()
0
>>> p.returncode
0

Code: Alles auswählen

>>> p = subprocess.Popen("echo $HOME", shell=True)
/home/rbreu
>>> p.returncode
>>> p.returncode
>>> p.returncode
>>> p.returncode
>>> p.returncode
>>> p.returncode
>>> p.poll()
0
>>> p.returncode
0
Macht natuerlich sinn, dass man aktiv pollen muss, aber das geht nicht so direktk aus der Doku hervor.

Verfasst: Mittwoch 16. April 2008, 12:48
von droptix
Ah genau, das scheint es zu sein. Ohne `poll()` bleibt `returncode` u.U. immer auf `None`. Nun geht's, super! :D

Verfasst: Mittwoch 16. April 2008, 13:52
von keppla
Anstatt poll lieber explizit aufs ende des prozesses (mit .wait()) warten, das scheint das gleiche zu sein.

Verfasst: Mittwoch 16. April 2008, 13:58
von BlackJack
@keppla: `wait()` blockiert aber. Die Aufgabenstellung war ja nach x Sekunden nachsehen ob Prozess fertig, wenn nein -> töten.

Verfasst: Mittwoch 16. April 2008, 14:31
von droptix
Genau!

Noch ein Problem: Unter Windows bekomme ich mittels so eine falsche PID zurück:

Code: Alles auswählen

process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print process.pid
Geprinted wird z.B. 1740, der TaskManager zeigt aber 2112. Wie das?

Unter Linux muss ich das erst noch testen...

Verfasst: Mittwoch 16. April 2008, 14:32
von droptix
Aaaah, es wird die PID der Shell zurück gegeben. So muss man das machen:

Code: Alles auswählen

process = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print process.pid
[Edit:] Nein, das ist aber auch Mist! So kann ich keine Shell-Befehle wie `dir` mehr ausführen. Da kommt dann sofort dieser Fehler hier:

<type 'exceptions.WindowsError'>:[Error 2] Das System kann die angegebene Datei nicht finden

Also muss ich `shell=True` lassen, dann klappen zumindest alle Befehle... aber ich kann ein GUI-Programm so z.B. nicht nach einem Timeout beenden. Jemand ne Idee dazu?

Verfasst: Mittwoch 16. April 2008, 14:56
von keppla
BlackJack hat geschrieben:@keppla: `wait()` blockiert aber. Die Aufgabenstellung war ja nach x Sekunden nachsehen ob Prozess fertig, wenn nein -> töten.
Uff, sorry, hab das irgendwie auf "was ist der returncode" reduziert. :oops:

Verfasst: Mittwoch 16. April 2008, 14:58
von Rebecca
Anscheinend ist dir (im Gegensatz zu ls unter Linux) kein alleinstehendes Programm, sondern eine Shell-Anweisung.

Warum moechtest du ueberhaupt aus Python heraus Shell-Anweisungen ausfuehren? Immerhin programmierst du ja Python... :wink:

Verfasst: Mittwoch 16. April 2008, 15:03
von droptix
Rebecca hat geschrieben:Warum moechtest du ueberhaupt aus Python heraus Shell-Anweisungen ausfuehren?
Weil es geht... nein ernsthaft: ich bastle eine einfache Python-Shell, um auf einen entfernten Rechner zugreifen zu können.

Idee von mir: alle Prozesse beenden, die als Parent-PID die zurück gegebene PID (also die der Shell) besitzen. Das könnte vielleicht klappen...

Re: subprocess -> wann beendet?

Verfasst: Mittwoch 16. April 2008, 16:07
von jens
droptix hat geschrieben:Ich möchte einen Timeout festlegen, um den Sub-Prozess ggf. automatisch zu beenden, wenn er z.B. nach 15 Sekunden nicht "antwortet".
Ich hab sowas mal gemacht, siehe:
http://www.python-forum.de/post-23692.html#23692
http://www.pylucid.net:8080/pylucid/bro ... ls.py#L477

Re: subprocess -> wann beendet?

Verfasst: Mittwoch 16. April 2008, 16:16
von droptix
jens hat geschrieben:Ich hab sowas mal gemacht
Danke, aber dient mir leider überhaupt nicht. Ich will ja nicht abwarten, bis der Prozess tatsächlich gekillt wurde und hinterher darauf reagieren. Außerdem ist es komisch zu sagen "Wenn der Prozess 30 Sekunden dauerte und dann mit returncode 0 (= i.d.R. sauberer Exit ohne Fehler) endete, dann wurde er wahrscheinlich gekillt." -> stimmt ja höchst wahrscheinlich nicht und ist total vom Prozess abhängig.

Ich möchte den Prozess gewaltsam beenden, wenn er mir zu lange dauert (oder eben wirklich hängt).

Verfasst: Mittwoch 16. April 2008, 16:21
von jens
Der sourcecode ist schon alt... Der Prozess wird gewaltsam gekillt, nachdem der timeout überschritten wird.

EDIT: Aber:
Da os.kill() nur unter Linux und Mac verfügbar ist, funktioniert das
ganze nicht unter Windows :(

Verfasst: Mittwoch 16. April 2008, 17:37
von jens
Hab das ganze nochmal überarbeitet...
http://trac.pylucid.net/browser/trunk/p ... y?rev=1634

Dabei ist zu beachten, das man stdout und stderr nur dann lesen kann, wenn der Prozess normal beendet wurde. Wurde der Process abgeschossen, blockiert das .read()...

Eine Idee wie man das umgehen kann???

EDIT: Link aktualisiert.

Verfasst: Mittwoch 16. April 2008, 19:24
von lunar
SIGTERM nutzen.

Verfasst: Mittwoch 16. April 2008, 19:27
von jens
lunar hat geschrieben:SIGTERM nutzen.
Warum, kannst du das näher erläutern? Ich kenne mich da nicht so richtig aus...

Verfasst: Mittwoch 16. April 2008, 19:57
von droptix
jens hat geschrieben:Wurde der Process abgeschossen, blockiert das .read()...
Das Problem habe ich auch. Ich umgehe das einfach damit, dass ich .read() erst durchführe, wenn .returncode nicht gleich None ist (andernfalls greift sowieso der Timeout und killt den Prozess). Ich weiß aber nicht, wie viel Output dabei gepuffert wird.

Ich mache übrigens noch eine kleine Unterscheidung in Windows und Unix/Mac:

Code: Alles auswählen

TIMEOUT = 3
PLATFORM = sys.platform

if PLATFORM == "win32":
	import win32api
	import win32con
	import win32process
else:
	import os
	import signal

# ... somewhere else:

			process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
			t = time.time()
			while True:
				process.poll()
				if process.returncode is not None:
					output = process.stdout.read()
					returnCode = process.returncode
					break
				elif time.time() - t >= TIMEOUT:
					# try to kill process
					try:
						if PLATFORM == "win32":
							try:
								h = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, process.pid)
								handle = h.handle
								win32process.TerminateProcess(handle, 1)
								killed = process.pid
							except Exception, e:
								killed = e
						else:
							try:
								os.kill(process.pid, signal.SIGUSR1)
								killed = process.pid
							except Exception, e:
								killed = e
					finally:
						break
				time.sleep(0.5)

Verfasst: Mittwoch 16. April 2008, 21:15
von Rebecca
jens hat geschrieben:
lunar hat geschrieben:SIGTERM nutzen.
Warum, kannst du das näher erläutern? Ich kenne mich da nicht so richtig aus...
Es gibt ja viele verschiedene Signale. SIGTERM (15) ist quasi die freundliche Bitte an den Prozess, sich ordnungsgemaess zu beenden. Der Prozess kann dann noch aufraeumen, koennte das Signal sogar ignorieren. IMO wird z.B. ein SIGTERM gesendet, wenn du im Fenster-Titel auf das "x" clickst.

SIGKILL (9) hingegen ist ein hartes, sofortiges Abschiessen des Prozesses und sollte nur verwendet werden, wenn SIGTERM nicht mehr hilft.