2012-02-06 117 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。