Python Subprocess readline () зависает;не могу использовать нормальные параметры - PullRequest
1 голос
/ 20 октября 2019

Для начала, я знаю, это выглядит как дубликат. Я читал:

Readlines подпроцесса Python () зависает

Readline зависает подпрограммы Python () после чтения всего ввода

Подпроцесс readline зависает в ожидании EOF

Но эти опции либо не работают, либо я не могу их использовать.

Проблема

# Obviously, swap HOSTNAME1 and HOSTNAME2 with something real
cmd = "ssh -N -f -L 1111:<HOSTNAME1>:80 <HOSTNAME2>"

p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=os.environ)
while True:
    out = p.stdout.readline()
    # Hangs here ^^^^^^^ forever

    out = out.decode('utf-8')
    if out:
        print(out)
    if p.poll() is not None:
        break

Моя дилемма состоит в том, что функция, вызывающая subprocess.Popen(), является библиотечной функцией для запуска команд bash, поэтому она должна быть очень общей и иметь следующие ограничения:

  • Необходимо отображать вывод какэто входит;не блокировать, а затем спамить экран одновременно
  • Невозможно использовать многопроцессорную обработку, если родительский вызывающий объект выполняет многопроцессорную библиотечную функцию (Python не позволяет дочерним процессам иметь дочерние процессы)
  • Не могу использовать signal.SIGALRM по той же причине, что и многопроцессорная;родительский вызывающий абонент, возможно, пытается установить собственное время ожидания
  • Невозможно использовать сторонние не встроенные модули
  • Потоковая обработка не работает. Когда вызов readline() находится в потоке, thread.join(timeout=1) позволяет программе продолжаться, но ctrl + c вообще не работает, и вызов sys.exit() не завершает программу,так как поток все еще открыт. И, как вы знаете, вы не можете убить поток в python по своему замыслу.
  • Ни один из способов bufsize или других аргументов подпроцесса, кажется, не имеет значения;и не делает readline () в итераторе.

У меня было бы работоспособное решение, если бы я мог убить поток , но это супер табу, хотя это определенно законнослучай использования.

Я открыт для любых идей.

Ответы [ 2 ]

0 голосов
/ 21 октября 2019

Я наконец получил рабочее решение;ключевая часть информации, которую я пропустил, была thread.daemon = True, на которую @augurar указал в своем ответе.

Установка thread.daemon = True позволяет завершать поток при завершении основного процесса;поэтому разблокирование моего использования подпотока для мониторинга readline().

Вот пример реализации моего решения;Я использовал объект Queue() для передачи строк в основной процесс, и я реализовал 3-секундный таймер для случаев, подобных исходной проблеме, которую я пытался решить, когда подпроцесс завершился и завершился, но readline () зависает для некоторыхпричина.

Это также помогает избежать состояния гонки, между которым вещь заканчивается первым.

Это работает как для Python 2, так и для 3.

import sys
import threading
import subprocess
from datetime import datetime

try:
    import queue
except:
    import Queue as queue # Python 2 compatibility


def _monitor_readline(process, q):
    while True:
        bail = True
        if process.poll() is None:
            bail = False
        out = ""
        if sys.version_info[0] >= 3:
            out = process.stdout.readline().decode('utf-8')
        else:
            out = process.stdout.readline()
        q.put(out)
        if q.empty() and bail:
            break

def bash(cmd):
    # Kick off the command
    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)

    # Create the queue instance
    q = queue.Queue()
    # Kick off the monitoring thread
    thread = threading.Thread(target=_monitor_readline, args=(process, q))
    thread.daemon = True
    thread.start()
    start = datetime.now()
    while True:
        bail = True
        if process.poll() is None:
            bail = False
            # Re-set the thread timer
            start = datetime.now()
        out = ""
        while not q.empty():
            out += q.get()
        if out:
            print(out)

        # In the case where the thread is still alive and reading, and
        # the process has exited and finished, give it up to 3 seconds
        # to finish reading
        if bail and thread.is_alive() and (datetime.now() - start).total_seconds() < 3:
            bail = False
        if bail:
            break

# To demonstrate output in realtime, sleep is called in between these echos
bash("echo lol;sleep 2;echo bbq")
0 голосов
/ 20 октября 2019

Один из вариантов - использовать поток для публикации в очереди. Затем вы можете заблокировать очередь с таймаутом. Вы можете сделать читателя потоком демона, чтобы он не мешал выходу из системы. Вот эскиз:

import subprocess
from threading import Thread
from queue import Queue

def reader(stream, queue):
    while True:
        line = stream.readline()
        queue.put(line)
        if not line:
            break

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, ...)
queue = Queue()
thread = Thread(target=reader, args=(p.stdout, queue))
thread.daemon = True
thread.start()
while True:
    out = queue.get(timeout=1)  # timeout is optional
    if not out:  # Reached end of stream
        break
    ...  # Do whatever with output

# Output stream was closed but process may still be running
p.wait()

Обратите внимание, что вы должны адаптировать этот ответ к вашему конкретному варианту использования. Например, вы можете захотеть добавить сигнал для потока считывателя о прекращении работы до достижения конца потока.

Другой вариант - опросить входной поток, как в этом вопросе: тайм-аут на readline подпроцесса в python

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