subprocess.popen () перенаправление stderr с использованием каналов / сбоев - PullRequest
3 голосов
/ 06 февраля 2012

Я хочу запустить процесс, который может выдавать много выходных данных в течение тайм-аута, захватывая stdout / stderr. Использование capture() и PIPE в качестве stdout / stderr может привести к взаимоблокировке в соответствии с документацией для subprocess.

Теперь я в любом случае использую 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)

Но даже это не может зафиксировать ошибки , если процесс завершается, скажем, ядром (не хватает памяти и т. Д.).

Каково идеальное решение этой проблемы?

Ответы [ 2 ]

4 голосов
/ 06 февраля 2012

Вместо использования файлов для вывода вернитесь к использованию каналов, но используйте модуль fcntl , чтобы перевести p.stdout и p.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, чтобы убедиться, что это не настоящая ошибка.

2 голосов
/ 07 февраля 2012

Проблема с неблокирующим режимом состоит в том, что вы в конечном итоге заняты ожиданием ввода / вывода. Более традиционный подход заключается в использовании одного из вызовов select . Даже если у вас есть только один файловый дескриптор для чтения / записи, вы можете прикрепить к нему желаемое время ожидания, чтобы вы восстановили контроль после указанного интервала без дальнейших операций ввода-вывода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...