2014-01-17 4 views
10

Das folgende einfache Skript hängt zeitweise beim Aufruf von subprocess.Popen (ca. 30% der Zeit).
Es sei denn use_lock = True, und dann hängt es nie, führt mich zu glauben, Subprozess ist nicht threadsicher! Das erwartete Verhalten ist Skript endet innerhalb von 5-6 Sekunden.
Um den Fehler zu demonstrieren, starten Sie einfach "python bugProof.py" ein paar Mal, bis es aufhängt. Strg-C wird beendet. Sie werden sehen, dass die Post-Popen nur ein- oder zweimal, aber nicht zum dritten Mal erscheinen.Ist subprocess.Popen nicht threadsicher?

import subprocess, threading, fcntl, os, time 
end_time = time.time()+5 
lock = threading.Lock() 
use_lock = False 
path_to_factorial = os.path.join(os.path.dirname(os.path.realpath(__file__)),'factorial.sh') 

def testFunction(): 
    print threading.current_thread().name, '| pre-Popen' 
    if use_lock: lock.acquire() 
    p = subprocess.Popen([path_to_factorial], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
    if use_lock: lock.release() 
    print threading.current_thread().name, '| post-Popen' 
    fcntl.fcntl(p.stdout, fcntl.F_SETFL, os.O_NONBLOCK) 
    fcntl.fcntl(p.stderr, fcntl.F_SETFL, os.O_NONBLOCK) 
    while time.time()<end_time: 
     try: p.stdout.read() 
     except: pass 
     try: p.stderr.read() 
     except: pass 
    print threading.current_thread().name, '| DONE' 

for i in range(3): 
    threading.Thread(target=testFunction).start() 


Die Shell-Skript oben (factorial.sh) verwiesen:

#!/bin/sh 
echo "Calculating factorial (anything that's somewhat compute intensive, this script takes 3 sec on my machine" 
ans=1 
counter=0 
fact=999 
while [ $fact -ne $counter ] 
do 
    counter=`expr $counter + 1` 
    ans=`expr $ans \* $counter` 
done 
echo "Factorial calculation done" 
read -p "Test input (this part is critical for bug to occur): " buf 
echo "$buf" 

Systeminfo: Linux 2.6.32-358.123.2.openstack.el6.x86_64 # 1 SMP Do September 26 17:14:58 EDT 2013 x86_64 x86_64 x86_64 GNU/Linux
Python 2.7.3 (Standard, 22. Januar 2013, 11:34:30)
[GCC 4.4.6 20120305 (Red Hat 4.4.6-4) ] auf linux2

+0

Putting close_fds = True in der Popen-Aufruf auch das Problem aus irgendeinem Grund behebt. – Roman

+0

Das war buchstäblich die ärgerlichste Sache, die ich in Python in letzter Zeit erlebt habe. Sooo frustrierend. –

Antwort

13

In Python 2.x gibt es verschiedene Racebedingungen, die sich auf subprocess.Popen auswirken. (z. B. auf 2.7 deaktiviert es & stellt Garbage Collection wieder her, um verschiedene Timing-Probleme zu verhindern, aber das ist nicht thread-safe in sich). Siehe z.B. http://bugs.python.org/issue2320, http://bugs.python.org/issue1336 und http://bugs.python.org/issue14548 für einige der Probleme in diesem Bereich. In Python 3.2 wurde eine substanzielle Überarbeitung des Unterprozesses vorgenommen, die diese adressiert (unter anderem ist der exec-Code & in einem C-Modul, anstatt etwas vernünftigerweise Python-Code im kritischen Teil zwischen fork und exec zu machen). und ist verfügbar für aktuelle Python 2.x-Versionen im Modul subprocess32. Beachten Sie Folgendes auf der PyPI-Seite: "Auf POSIX-Systemen ist die Zuverlässigkeit in Thread-Anwendungen garantiert."

Ich kann die gelegentlichen (ca. 25% für mich) Abstürze des obigen Codes reproduzieren, aber nach der Verwendung von import subprocess32 as subprocess habe ich keine Fehler in mehr als 100 Läufen gesehen.

Beachten Sie, dass subprocess32 (und Python 3.2 +) standardmäßig auf close_fds = True, aber mit subprocess32 an Ort und Stelle, sah ich keine Fehler sogar mit close_fds = False (nicht, dass Sie in der Regel brauchen sollten).