фильтровать выход процесса с потенциально неограниченным выходом, определять код выхода и время ожидания после X

#python #django #subprocess #uwsgi

#python #django #подпроцесс #uwsgi

Вопрос:

У меня есть некоторый существующий код Django, работающий под uwsgi (под Linux) с отключенной обработкой потоков, который выполняет для некоторых запросов подпроцесс, который я не могу контролировать.

Обычная операция выполняется следующим образом:

  • подпроцесс выполняется в течение довольно короткого времени и возвращает либо код выхода 0, либо что-то другое. Код запишет некоторые сообщения в stdout / stderr. Код возврата (код выхода) сообщит мне, была ли работа выполнена правильно. если выполнение завершилось неудачно, было бы неплохо собрать stdout / stderr и зарегистрировать его, чтобы понять, почему что-то не удалось.

Однако в редких случаях подпроцесс может столкнуться с пока не понятым состоянием гонки и выполнит следующее.

  • он будет повторно записывать определенное сообщение в stdout и stderr и зацикливаться и зависать навсегда.

Поскольку я не знаю, существуют ли какие-либо другие условия гонки, которые могут заморозить процесс с выводом или без него. Id’ также хотел бы добавить тайм-аут. (Хотя решение, которое адресует получение кода возврата и обнаружение повторного сообщения, уже было бы хорошим достижением.

То, что я пробовал до сих пор, это:

 import os
import select
import subprocess
import time

CMD = ["bash", "-c", "echo hello"]
def run_proc(cmd=CMD, timeout=10):
    """ run a subprocess, fetch (and analyze stdout / stderr) and
        detect if script runs too long
        and exit when script finished
    """

    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout = proc.stdout.fileno()
    stderr = proc.stderr.fileno()

    t0 = time.time()
    while True:
        if time.time() - t0 > timeout:
            print("TIMEOUT")
            break
        rc = proc.returncode
        print("RC", rc)
        if proc.returncode is not None:
            break
        to_rd, to_wr, to_x = select.select([stdout, stderr], [], [], 2)
        print(to_rd, to_wr, to_x)
        if to_rd:
            if stdout in to_rd:
                rdata = os.read(stdout, 100)
                print("S:", repr(rdata))
            if stderr in to_rd:
                edata = os.read(stderr, 100)
                print("E:", repr(edata))
    print(proc.returncode)
 

На самом деле мне не нужно, чтобы stdout и stderr обрабатывались отдельно, но это ничего не изменило

Однако, когда подпроцесс завершил вывод, происходит что-то действительно странное.

 the output of select tells me, that stdout and stderr can be read from, but when I read I get an empty string.
proc.returncode is still None
 

Как я мог исправить приведенный выше код или как я мог бы сохранить свою проблему другим способом?

Ответ №1:

Проверьте хотя бы Popen.poll():

 def run_proc(cmd=CMD, timeout=10):
    """ run a subprocess, fetch (and analyze stdout / stderr) and
        detect if script runs too long
        and exit when script finished
    """

    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout = proc.stdout.fileno()
    stderr = proc.stderr.fileno()

    t0 = time.time()
    while True:
        returncode = proc.poll()
        print("RC", returnode)
        if returncode is not None:
            break

        if time.time() - t0 > timeout:
            print("TIMEOUT")
            # You need to kill the subprocess, break doesn't stop it!
            proc.terminate()
            # wait for the killed process to 'reap' the zombie
            proc.wait()
            break

        to_rd, to_wr, to_x = select.select([stdout, stderr], [], [], 2)
        print(to_rd, to_wr, to_x)
        if to_rd:
            if stdout in to_rd:
                rdata = os.read(stdout, 100)
                print("S:", repr(rdata))
            if stderr in to_rd:
                edata = os.read(stderr, 100)
                print("E:", repr(edata))
    print(returncode)
 

Выход:

 RC None
[3] [] []
S: b'hellon'
RC None
[3, 5] [] []
S: b''
E: b''
0
 

Комментарии:

1. большое спасибо. proc.poll() мне помогает. Однако while proc.poll() is None and proc.returncode is None: я думаю, что его можно было бы изменить на while proc.poll(): или, возможно, на while True: следующий rc = proc.poll() , и где-то if rc is not None: break

2. еще одна проблема. proc.terminate() кажется, оставляет зомби. Любой способ избежать этого. поскольку uwsgi работает очень долго, существует теоретический риск оставить после себя довольно много зомби

3. proc.terminate() после proc.wait() этого, похоже, устраняется проблема с зомби

4. Я повысил ваш ответ. Не могли бы вы, пожалуйста, адаптировать свой ответ так, чтобы proc.terminate() proc.wait() вызывался и, и, возможно, изменить оператор while, чтобы было понятнее, что результатом proc.poll() является proc.returncode или, если вы предпочитаете, я мог бы добавить эти изменения в ваш ответ, а затем пометить этот ответ как решение

5. @gelonida: Отредактируйте мой ответ, пожалуйста.