Eine interne Bibliothek, die subprocess.Popen() verwendet, begann die automatischen Tests fehlgeschlagen, wenn wir von Python 2.7.3 auf Python 2.7.5 aktualisiert. Diese Bibliothek wird in einer Umgebung mit Threads verwendet. Nach dem Debuggen des Problems konnte ich ein kurzes Python-Skript erstellen, das den Fehler wie in den fehlgeschlagenen Tests zeigt.Popen von Subprozess schließt stdout/stderr filedescriptors, die in einem anderen Thread verwendet werden, wenn Popen Fehler
Dies ist das Skript (genannt "threadedsubprocess.py"):
import time
import threading
import subprocess
def subprocesscall():
p = subprocess.Popen(
['ls', '-l'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
time.sleep(2) # simulate the Popen call takes some time to complete.
out, err = p.communicate()
print 'succeeding command in thread:', threading.current_thread().ident
def failingsubprocesscall():
try:
p = subprocess.Popen(
['thiscommandsurelydoesnotexist'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except Exception as e:
print 'failing command:', e, 'in thread:', threading.current_thread().ident
print 'main thread is:', threading.current_thread().ident
subprocesscall_thread = threading.Thread(target=subprocesscall)
subprocesscall_thread.start()
failingsubprocesscall()
subprocesscall_thread.join()
Hinweis: dieses Skript mit einem IOError nicht beendet hat, wenn sie von Python 2.7.3 lief. Es schlägt mindestens 50% der Zeiten fehl, wenn es von Python 2.7.5 ausgeführt wurde (beide auf derselben Ubuntu 12.04 64-Bit-VM).
Der Fehler, die 2.7.5 auf Python angehoben wird, ist dies:
/opt/python/2.7.5/bin/python ./threadedsubprocess.py
main thread is: 139899583563520
failing command: [Errno 2] No such file or directory 139899583563520
Exception in thread Thread-1:
Traceback (most recent call last):
File "/opt/python/2.7.5/lib/python2.7/threading.py", line 808, in __bootstrap_inner
self.run()
File "/opt/python/2.7.5/lib/python2.7/threading.py", line 761, in run
self.__target(*self.__args, **self.__kwargs)
File "./threadedsubprocess.py", line 13, in subprocesscall
out, err = p.communicate()
File "/opt/python/2.7.5/lib/python2.7/subprocess.py", line 806, in communicate
return self._communicate(input)
File "/opt/python/2.7.5/lib/python2.7/subprocess.py", line 1379, in _communicate
self.stdin.close()
IOError: [Errno 9] Bad file descriptor
close failed in file object destructor:
IOError: [Errno 9] Bad file descriptor
Wenn das Subprozess Modul aus Python 2.7.3 bis 2.7.5 Python I die Popen siehe Vergleichen() 's __init __() call schließt nun explizit die stdin, stdout und stderr Dateideskriptoren für den Fall, dass die Ausführung des Befehls fehlschlägt. Dies scheint ein beabsichtigter Fix zu sein, der in Python 2.7.4 angewendet wurde, um undichte Dateideskriptoren zu verhindern (http://hg.python.org/cpython/file/ab05e7dd2788/Misc/NEWS#l629).
Die diff zwischen Python 2.7.3 und Python 2.7.5, die zu diesem Thema relevant zu sein scheinen, ist in dem Popen __init __():
@@ -671,12 +702,33 @@
c2pread, c2pwrite,
errread, errwrite) = self._get_handles(stdin, stdout, stderr)
- self._execute_child(args, executable, preexec_fn, close_fds,
- cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite)
+ try:
+ self._execute_child(args, executable, preexec_fn, close_fds,
+ cwd, env, universal_newlines,
+ startupinfo, creationflags, shell,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite)
+ except Exception:
+ # Preserve original exception in case os.close raises.
+ exc_type, exc_value, exc_trace = sys.exc_info()
+
+ to_close = []
+ # Only close the pipes we created.
+ if stdin == PIPE:
+ to_close.extend((p2cread, p2cwrite))
+ if stdout == PIPE:
+ to_close.extend((c2pread, c2pwrite))
+ if stderr == PIPE:
+ to_close.extend((errread, errwrite))
+
+ for fd in to_close:
+ try:
+ os.close(fd)
+ except EnvironmentError:
+ pass
+
+ raise exc_type, exc_value, exc_trace
Ich glaube, ich drei Fragen:
1) Stimmt es, dass es grundsätzlich möglich ist, subprocess.Popen mit PIPE für stdin, stdout und stderr in einer Thread-Umgebung zu verwenden?
2) Wie verhindere ich, dass die Dateideskriptoren für stdin, stdout und stderr geschlossen werden, wenn Popen() in einem der Threads fehlschlägt?
3) Mache ich hier etwas falsch?
Coole Sache ist, dass dies in Python fixiert ist 3.4+ – Cukic0d