简体   繁体   English

subprocess.popen()使用管道/失败的stderr重定向

[英]subprocess.popen() stderr redirection with pipes / fails

I want to run a process which may produce a lot of output for up to timeout seconds, capturing the stdout / stderr . 我想运行一个进程,它可以产生大量输出,最多超时秒,捕获stdout / stderr Using capture() and PIPE as stdout / stderr is prone to deadlocking according to the documentation for subprocess . 根据subprocess文档,使用capture()PIPE作为stdout / stderr很容易发生死锁。

Now, I'm using poll() anyways -- because I want to be able to kill the process after the timeout -- but I still don't know how to avoid the deadlock using PIPE. 现在,我正在使用poll() - 因为我希望能够在超时后终止进程 - 但我仍然不知道如何使用PIPE来避免死锁。 How do I do that? 我怎么做?

Currently I'm just working around by creating tempfiles: 目前我只是通过创建临时文件来解决:

#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)

But even this fails to capture errors if the process is killed by, say, the kernel (out of memory, etc.). 但是,如果进程被内核(内存不足等)杀死,即使这样也无法捕获错误

What's the ideal solution to this problem? 什么是这个问题的理想解决方案?

Instead of using files for the output, go back to using pipes but use the fcntl module to put p.stdout and p.stderr into non-blocking mode. 不使用文件作为输出,而是返回使用管道,但使用fcntl模块将p.stdoutp.stderr变为非阻塞模式。 This will cause p.stdout.read() and p.stderr.read() to return whatever data is available or raise an IOError if there is no data, instead of blocking: 这将导致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)

As glglgl pointed out in comments, you should do some additional checking in the except IOError clause to make sure that it is not actually a real error. 正如glglgl在注释中指出的那样,你应该在except IOError子句中做一些额外的检查,以确保它实际上不是真正的错误。

The trouble with non-blocking mode is that you end up busy-waiting for I/O. 非阻塞模式的问题在于您最终忙于等待I / O. The more conventional approach is to use one of the select calls. 更常规的方法是使用其中一个选择呼叫。 Even if you have only one file descriptor to read/write, you can stick your desired timeout on it, so you regain control after the specified interval with no further I/O. 即使您只有一个文件描述符可供读/写,您也可以在其上粘贴所需的超时,这样您就可以在指定的时间间隔后重新获得控制权,而无需进一步的I / O.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM