Модуль подпроцесса Rust и Python с stdin.readline

Минимальный воспроизводимый пример

Я создал минимальный воспроизводимый пример, который можно клонировать и легко запустить на Github для тестирования: https://github.com/VirxEC/python-no-run

Если вы не хотите переходить на GitHub, фрагменты кода будут внизу этого поста.

Проблема, описанная как можно быстрее

При запуске Python из Rust в Windows (Linux работает нормально) чтение из стандартного ввода не позволяет подпроцессу Python запускать больше подпроцессов Python. Добавление быстрого sleep(1) позволяет запустить подпроцесс, и он работает нормально, но если для запуска подпроцесса требуется более 1 секунды, мне не повезло. Если это занимает менее 1 секунды, значит, я жду дольше, чем должен. Итак, использование sleep — это быстрый патч, который я реализовал на данный момент, но мне нужно правильное решение.

Проблема, описанная более подробно

Когда и немного почему

Эта проблема возникает только тогда, когда Python запускается из Rust только в Windows, и если вы запускаете Python в обычном режиме, все работает как положено. Однако мое приложение не является приложением Python, это графический интерфейс Rust, использующий Tauri с подкомпонентом Python, потому что есть большая библиотека, предназначенная только для Python и еще не портированная на Rust... и, возможно, никогда не будет портирована из-за к своим большим размерам.

Это также означает, что я не могу принимать другие несвязанные команды из основного процесса Rust, пока он порождает подпроцессы, потому что он просто не читает стандартный ввод и не ищет их. В моей реальной программе я запускаю новый «поток» из потоковой обработки, поэтому теоретически стандартный ввод может быть прочитан, в то время как подпроцессы запускаются на стороне. Теперь я не должен позволять чтению stdin во время порождения этих процессов, что просто добавляет дополнительную сложность, которая не нужна.

Что должно произойти по сравнению с тем, что происходит

Проблема в том, что «Hello World» не рассылается спамом, потому что подпроцесс запускается, но не начинает выполняться. Я был в этом, и я в недоумении, что происходит. Я знаю, что это связано с stdin, но моя программа использует stdin и stdout для связи между основным процессом Rust и процессом Python.

Каким-то образом чтение со стандартного ввода в основном потоке блокирует запуск подпроцесса полностью. Я знаю это, потому что если вы создадите подпроцесс с помощью shell=True, то после выхода из цикла «Hello World» будет рассылаться спамом.

Также, если я добавлю sleep(1) сразу после создания подпроцесса, то все работает! Ура! Вот только это далеко от идеала. Это как пластырь, который недостаточно велик для раны, но, по крайней мере, частично ее закрывает. Я временно внедрил это решение в свою программу (в частности, sleep(3) для каждого порожденного подпроцесса), но возможно, что потребуется порождать многие десятки процессов, что в конечном итоге займет слишком много времени. Кроме того, для запуска процесса все еще может потребоваться больше 3 секунд, и мне бы не повезло, если бы это произошло.

Код borked (только для Windows!)

Я удалил операторы импорта и т. д., потому что они не должны иметь большого значения. Их все еще можно найти в репозитории GitHub, где находится весь минимальный воспроизводимый пример, если вам интересно.

main.rs

// Setup command
let mut command = Command::new("python");
command.args(["-u", "-c", "from main import start; start()"]);
command.current_dir(std::env::current_dir()?.join("python"));

// Pipe stdin so we can issue commands to tell Python what we want it to do
command.stdin(Stdio::piped());

// Spawn the process & take ownership of stdin
let mut child = command.spawn()?;
let mut stdin = child.stdin.take().unwrap();

// Give a second to make 100% sure Python has started
sleep(Duration::from_secs(1));

// Issue start command to Python, wait 5 seconds then tell it to stop

println!("Writing start");
stdin.write_all(b"start | \n")?;

sleep(Duration::from_secs(5));

println!("Writing stop");
stdin.write_all(b"stop | \n")?;

// Make sure the process has exited before we exit
child.wait()?;

main.py

def start():
    procs = []
    while True:
        print("Listening for new command...")
        command = sys.stdin.readline()
        params = command.split(" | ")
        print(f"Got command: {command}")

        if params[0] == "stop":
            print("Stopping...")
            # Break from loop
            break
        elif params[0] == "start":
            print("Starting...")
            # Spawn the subprocess
            proc = subprocess.Popen([sys.executable, "say-hi.py"])

            # Add the subprocess to the list so we can kill it later
            procs.append(proc)

    print("Shutting down")
    # Terminate processes
    for proc in procs:
        proc.terminate()

say-hi.py

while True:
    print(f"Hello world!", flush=True)
    sleep(1)

Мой запущенный процесс

Это похоже на инструкции, которые можно найти в README.md в репозитории GitHub.

В корневой папке

  • Запустите cargo r и убедитесь, что ничего не происходит
  • В python/main.py замените Popen([sys.executable, "say-hi.py"]) на Popen([sys.executable, "say-hi.py"], shell=True)
  • Запустите cargo r и увидите, что «Hello world» рассылается спамом, когда выдается команда остановки, и никогда не останавливается.

В папке python

  • shell может быть True или False, не важно
  • Запустите python -c "from main import start; start()", чтобы начать процесс
  • Введите start | точно и увидите, что "Hello world" рассылается спамом
  • Введите stop | точно и увидите, что сообщение «Hello world» перестало рассылаться спамом, все успешно обрабатываются.

Вы уверены, что ржавчина очищает стандартный ввод процесса, а не буферизует его?

Ahmed AEK 29.10.2022 20:24

@AhmedAEK Попробуйте запустить его сами - есть причина, по которой я помещаю туда другие отпечатки, такие как print("Starting..."). Эти отпечатки отображаются просто отлично, потому что команды без вопросов принимаются через стандартный ввод. Просто "Hello World" не отображается.

VirxEC 29.10.2022 20:30

похоже, что python неправильно подключал детей к стандартному выводу, то есть: proc = subprocess.Popen([sys.executable, "say-hi.py"],stdin=subprocess.PIPE,stdout=sys.stdout,stderr=‌​sys.stderr) будет работать.

Ahmed AEK 29.10.2022 20:36

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

Ahmed AEK 29.10.2022 20:39

Нет, процессы не запускаются. Если вы выполните shell=True и запустите программу, спам Hello World начнется после завершения программы и не остановится (не знаю, почему это так, но я сказал это в посте)

VirxEC 29.10.2022 20:43

правильное подключение stdout и stderr устранило проблему на моей машине в Windows как с shell = True, так и без него ... вы пробовали? примечание: запуск python3.9 в Windows 10

Ahmed AEK 29.10.2022 20:55

Хорошо, тогда я попробую. Я выхожу с работы через несколько часов. Не уверен, почему это исправит это, но я не уверен, почему эта ошибка происходит так: p

VirxEC 29.10.2022 20:58

Если вы хотите дать ответ, чтобы я мог его принять, тогда вперед.

VirxEC 29.10.2022 20:58
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
8
124
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

subprocess.Popen с параметрами по умолчанию запрашивает дескриптор stdout и stdin из операционной системы в Windows ... который по какой-то причине зависает или терпит неудачу.

в любом случае передача родительского stdout или PIPE, похоже, исправляет это в Windows, просто заменяя

proc = subprocess.Popen([sys.executable, "say-hi.py"])

с

proc = subprocess.Popen([sys.executable, "say-hi.py"], stdin=subprocess.PIPE, stdout=sys.stdout, stderr=sys.stderr)

который просто передает идентификаторы родительского файла stdout дочернему для использования.

Спасибо за это! Теперь это имеет больше смысла, когда я знаю, что stdin=subprocess.PIPE было единственным, что мне было нужно... оно работает без stdout=sys.stdout и stderr=sys.stderr. Супер большое Вам спасибо!!!

VirxEC 30.10.2022 01:55

Другие вопросы по теме