Pythonic способ отсоединить процесс?

Я запускаю процесс etcd, который остается активным, пока вы его не завершите. (Он не предоставляет опцию режима демона.) Я хочу отсоединить его, чтобы я мог продолжать запускать больше Python.

Что бы я сделал в оболочке;

etcd & next_cmd

Я использую библиотеку Python sh по восторженной рекомендации всего Интернета. Я бы не хотел углубляться в subprocess или Popen, но я не нашел решений, использующих их.

Что я хочу;

sh.etcd(detach=True)
sh.next_cmd()

or

sh.etcd("&")
sh.next_cmd()

К сожалению, detach не является kwarg, и sh рассматривает "&" как флаг для etcd.

Я что-то пропустил здесь? Как это сделать?


person Alex Altair    schedule 29.05.2015    source источник


Ответы (5)


Чтобы реализовать & sh, избегайте программирования культа карго и используйте модуль subprocess напрямую:

import subprocess

etcd = subprocess.Popen('etcd') # continue immediately
next_cmd_returncode = subprocess.call('next_cmd') # wait for it
# ... run more python here ...
etcd.terminate() 
etcd.wait()

Это игнорирует обработку исключений и ваш разговор о «режиме демона» (если вы хотите реализовать демона в Python, используйте python-daemon. Чтобы запустить процесс как системную службу, используйте все, что предоставляет ваша ОС, или управляющую программу, такую ​​как supervisord).

person jfs    schedule 29.05.2015
comment
Использование subprocess.Popen('python file.py') в Windows возвращает FileNotFoundError: [Errno 2] No such file or directory: 'python file.py'. В чем может быть проблема? - person Dr_Zaszuś; 01.03.2018
comment
Это похоже на хороший отдельный вопрос. Связано: Вызов скрипта Python с входными данными в скрипте Python с использованием подпроцесса - person jfs; 01.03.2018
comment
@Dr_Zaszuś Попробуйте добавить shell=True в качестве kwarg к вашему вызову Popen, так как команда состоит из нескольких слов. Или вы можете передать слова в виде списка строк. docs.python.org/3.7/library/subprocess.html#subprocess. Открыть - person Taylor Edmiston; 07.08.2018

Автор ш здесь. Я полагаю, вы хотите использовать параметр специального ключевого слова _bg http://amoffat.github.io/sh/#background-processes

Это разветвит вашу команду и немедленно вернется. Процесс будет продолжать работать даже после выхода вашего скрипта.

person user48206    schedule 30.05.2015

Обратите внимание, что в следующих двух примерах есть вызов time.sleep(...), чтобы дать etcd время завершить запуск, прежде чем мы отправим ему запрос. Реальное решение, вероятно, включало бы проверку конечной точки API, чтобы узнать, доступна ли она, и зацикливание, если нет.

Вариант 1 (злоупотребление модулем multiprocessing):

import sh
import requests
import time

from multiprocessing import Process

etcd = Process(target=sh.etcd)

try:
    # start etcd
    etcd.start()
    time.sleep(3)

    # do other stuff
    r = requests.get('http://localhost:4001/v2/keys/')
    print r.text
finally:
    etcd.terminate()

Это использует модуль multiprocessing для обработки механики создания фоновых задач. Используя эту модель, вы не увидите вывод etcd.

Вариант 2 (проверенный):

import os
import signal
import time
import requests

pid = os.fork()
if pid == 0:
    # start etcd
    os.execvp('etcd', ['etcd'])

try:
    # do other stuff
    time.sleep(3)
    r = requests.get('http://localhost:4001/v2/keys/')
    print r.text
finally:
    os.kill(pid, signal.SIGTERM)

Здесь используется традиционная модель fork и exec, которая работает в Python так же хорошо, как и в C. В этой модели вывод etcd будет отображаться на вашей консоли, что может быть, а может и не быть тем, что вам нужно. Вы можете контролировать это, перенаправляя stdout и stderr в дочернем процессе.

person larsks    schedule 29.05.2015
comment
аналогично этому решению, в котором используются multiprocessing и os.fork() - person Trevor Boyd Smith; 09.07.2019

подпроцесс достаточно прост, чтобы сделать это тоже:

Этот подход работает (python3). Ключ использует start_new_session=True

ОБНОВЛЕНИЕ: несмотря на то, что документы Popen говорят, что это работает, это не так. Я обнаружил, разветвив ребенка, а затем выполнив os.setsid(), он работает так, как я хочу.

клиент.py:

#!/usr/bin/env python3
import time
import subprocess
subprocess.Popen("python3 child.py", shell=True, start_new_session=True)
i = 0
while True:
    i += 1
    print("demon: %d" % i)
    time.sleep(1)

ребенок.py:

#!/usr/bin/env python3
import time
import subprocess
import os

pid = os.fork()
if (pid == 0):
    os.setsid()

    i = 0
    while True:
        i += 1
        print("child: %d" % i)
        time.sleep(1)
        if i == 10:
            print("child exiting")
            break

выход:

./client.py
demon: 1
child: 1
demon: 2
child: 2
^CTraceback (most recent call last):
  File "./client.py", line 9, in <module>
    time.sleep(1)
KeyboardInterrupt

$ child: 3
child: 4
child: 5
child: 6
child: 7
child: 8
child: 9
child: 10
child exiting
person Neil McGill    schedule 26.01.2021
comment
похоже, вы скопировали этот ответ во все подобные вопросы о стеке, но это неправильно - он не отделяет подпроцесс от его родителя, он просто создает подпроцесс (в linux, python 3.8) - person Roy Shilkrot; 17.02.2021
comment
Немного запутанно, как говорят документы Popen. Если start_new_session имеет значение true, системный вызов setsid() будет выполнен в дочернем процессе до выполнения подпроцесса. (Только POSIX) Библиотечная функция setsid() позволяет процессу отсоединиться от своего родителя:. Так что это должно работать. По общему признанию, я тестировал на Mac. Хорошо, мне нужно проверить... - person Neil McGill; 18.02.2021
comment
Вы правы... Хорошо, я нашел обходной путь, снова разветвив ребенка и выполнив os.setsid. Кажется, это работает - вы можете подтвердить? Это звучит как ошибка Popen, как я видел во многих местах. Что касается Python 3.2, вы можете использовать subprocess.Popen() и передать start_new_session=True, чтобы полностью отсоединить дочерний процесс от родительского. - person Neil McGill; 18.02.2021

Публикуя это, если только по той причине, что найду его в следующий раз, когда я погуглю тот же вопрос:

 if os.fork() == 0:
    os.close(0)
    os.close(1)
    os.close(2)
    subprocess.Popen(('etcd'),close_fds=True)
    sys.exit(0)

Popen close_fds закрывает дескрипторы файлов, отличные от 0,1,2, поэтому код закрывает их явно.

person gerardw    schedule 31.07.2020