2012-02-06 120 views
3

我想運行一個進程,可能會產生大量的輸出達到超時秒數,捕獲stdout/stderr。根據documentation for subprocess,使用capture()PIPE作爲stdout/stderr容易發生死鎖。subprocess.popen()stderr重定向與管道/失敗

現在,我使用poll()反正 - 因爲我希望能夠在超時後終止進程 - 但我仍然不知道如何使用PIPE避免死鎖。我怎麼做?

目前我只是通過創建臨時文件工作圍繞:

#because of the shitty api, this has to be a file, because std.PIPE is prone to deadlocking with a lot of output, and I can't figure out what to do about it 
out, outfile = tempfile.mkstemp() 
err, errfile = tempfile.mkstemp() 

now = datetime.datetime.now().strftime('%H:%M, %Ss') 
print "Running '" + exe + "' with a timeout of ", timeout , "s., starting at ", now 
p = subprocess.Popen(args = exe, 
        stdout = out, 
        #for some reason, err isn't working if the process is killed by the kernel for, say, using too much memory. 
        stderr = err, 
        cwd = dir) 

start = time.time() 

# take care of infinite loops 
sleepDuration = 0.25 
time.sleep(0.1) 
lastPrintedDuration = 0 
duration = 0 
while p.poll() is None: 
    duration = time.time() - start 
    if duration > lastPrintedDuration + 1: 
     lastPrintedDuration += 1 
     #print '.', 
     sys.stdout.flush() 
    if duration >= timeout: 
     p.kill() 
     raise Exception("Killed after " + str(duration) + "s.") 
    time.sleep(sleepDuration) 

if p.returncode is not 0: 
    with open(errfile, 'r') as f: 
     e = f.read() 
     #fix empty error messages 
     if e == '': 
      e = 'Program crashed, or was killed by kernel.' 
     f.close() 

    os.close(out) 
    os.close(err) 
    os.unlink(outfile) 
    os.unlink(errfile) 
    print "Error after " + str(duration) + 's: ', 
    print "'" + e + "'" 
    raw_input('test') 
    raise Exception(e) 
else: 
    print "completed in " + str(duration) + 's.' 

os.close(out) 
os.close(err) 
os.unlink(outfile) 
os.unlink(errfile) 

但是,即使這未能捕捉到錯誤如果進程,比方說打死,內核(內存等) 。

這個問題的理想解決方案是什麼?

+0

+1閱讀文檔。 – 2012-02-06 18:48:02

回答

3

而不是使用文件的輸出,回到使用管道,但使用fcntl模塊將p.stdoutp.stderr進入非阻塞模式。這將導致p.stdout.read()p.stderr.read()返回任何數據可用或引發IOError如果沒有數據,而不是阻塞:

import fcntl, os 

p = subprocess.Popen(args = exe, 
        stdout = subprocess.PIPE, 
        stderr = subprocess.PIPE, 
        cwd = dir) 
fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) 
fcntl.fcntl(p.stderr.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) 

outdata, errdata = '', '' 
while p.poll() is None: 
    try: 
     outdata += p.stdout.read() 
    except IOError: 
     pass 
    try: 
     errdata += p.stderr.read() 
    except IOError: 
     pass 
    time.sleep(sleepDuration) 

由於glglgl在評論中指出,你應該做一些額外的檢查except IOError子句中以確保它不是真正的錯誤。

+1

這太簡單了。不是每個IOError都應該被忽略,只是'EAGAIN'。 – glglgl 2012-02-06 20:36:20

+0

fcntl調用真的有必要嗎?沒有這個電話會發生什麼? – 2012-02-10 13:38:30

+0

@hrehfeld - read()調用會阻塞,直到進程完成或無限期(取決於緩衝)爲止,所以在超時之後您將無法終止進程。 – 2012-02-10 17:03:55

2

非阻塞模式的問題在於您最終忙於等待I/O。更傳統的方法是使用select調用之一。即使您只有一個文件描述符可讀寫,您仍可以將所需的超時時間保留在其上,這樣您可以在指定的時間間隔內重新獲得控制權,而無需進一步的I/O。