воскресенье, 8 сентября 2013 г.

Запуск Python программы открепленной от консоли

Иногда бывает так, что нужно что бы программа выполнялась в фоне т.е. не была бы привязана к тому месту откуда была вызвана, например при написании демонов на UNIX системах и сервисоподобного ПО в Windows системах.

Вопросы создания процесса и его поведения достаточно отличаются в этих ОС. В случае с UNIX достаточно такого распространенного приема как "Magic double fork". В случае с Windows ОС дело обстоит сложнее, одним из вариантов является превратить процесс в службу (service) Windows, для этого можно воспользоваться библиотекой pywin32, которая за вас реализует цепочку вызовов Windows API. Однако это библиотека достаточно тяжела и избыточна функционалом, если необходимо решить только заданную задачу.

Суть варианта, рассматриваемого далее, состоит в том что на этапе инициализации (т.е. до запуска основного кода) происходит запуск дочерней идентичной программы и выходу из родительской. Таким образом дочерняя программа продолжает выполнение не имея стандартных потоков ввода/вывода.

Примечание для Windows: Для корректной работы кода Python интерпретатор должен быть прописан в переменной окружения PATH, что бы его можно было вызвать из командной строки Windows
#!/usr/bin/python
# -*- coding: utf-8 -*-
import optparse, os, sys, platform, subprocess

class Main(object):
    def __init__(self):
        # Парсер аргументов командной строки
        parser=optparse.OptionParser('Demo of cross-platform detouch console')
        parser.add_option('-v', '--verbose', action='store_true', default=False, help='Verbose ouput to stdout.')
        parser.add_option('-s', '--silence', action='store_true', default=False, help='Be silence, no any output to stdout and stderr')
        
        # Опция ниже является "флагом" указывающим что произошел перезапуск программы открепленной от консоли.
        parser.add_option('--windows-daemon', action='store_true', default=False, help=optparse.SUPPRESS_HELP)
        
        command_line_options, self.arguments=parser.parse_args()

        # В случае если платформа ОС LINUX/UNIX то выполняем "magic double fork"
        if platform.system()=='Linux':
            # set daemon via magic double fork
            try:
                pid=os.fork()
                if pid>0:
                    sys.exit(0)
            except OSError, e:
                print 'fork #1 failed: {} {}'.format(e.errno, e.strerror)
                sys.exit(1)
            
            # Do second fork
            try:
                pid=os.fork()
                if pid>0:
                    print 'Daemon PID {}'.format(pid)
                    sys.exit(0)
            except OSError, e:
                print 'fork #2 failed: {} {}'.format(e.errno, e.strerror)
                sys.exit(1)
        else:
            # Если аргумент командой строки не задан значит программа вызывается первый раз,
            # следовательно, выполняем перезапуск указывая необходимый алгоритм.
            if not self.options.windows_daemon:
                p=subprocess.Popen([sys.executable or 'python']+sys.argv+['--windows-daemon'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                time.sleep(2)            
                if p.poll()!=None:
                    print p.stderr.read()
                    print p.stdout.read()
                    exit(1)
                exit(0)

6 комментариев:

  1. optparse Deprecated since version 2.7
    такой подход с чтением из потоков может зависать, например, при чтении из stderr когда туда ничего не выводится
    рабочий вариант сделать так
    p=subprocess.Popen(['python']+sys.argv+['--windows-daemon'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    и читать всё из одного потока
    а почему такая проверка на None? обычно проверяется is None/is not None
    За пост спасибо, как раз искал как можно сделать службу из скрипта, но в основном встречал вариант с pywin32

    ОтветитьУдалить
    Ответы
    1. >>optparse Deprecated since version 2.7
      так то да, но почему то до сих пор он есть и в 3й ветке). Официально предпочтительнее использовать argparse.
      >>такой подход с чтением из потоков может зависать, например, при чтении из stderr когда туда ничего не выводится
      нет, если туда ничего не выводится он вернет пустую строку. Не забываем к тому же что по коду это уже завершенный процесс и следовательно все потоки (а ведь они файло-подобные объекты) закрыты. А ваш предложенный вариант плох, например когда приложение сложнее и вывод с потоком нужно далее как то обрабатывать (помним что не едином print'ом живы)
      >>а почему такая проверка на None? обычно проверяется is None/is not None
      ну как мне кажется это не принципиально, конструкция is это такой Питонячий подход, у меня же использован из классических ЯП. Ничего принципиального.
      >>За пост спасибо
      пожалуйста)

      Удалить
    2. Да я не заметил что работа идёт с уже завершенным процессом. Но насколько я помню для этого используется communicate. Но при выводе с процесса который работает в данный момент, виснуть всё таки будет.

      Удалить
    3. >>Но насколько я помню для этого используется communicate.
      вы плохо поняли код, вовсе не нужно ждать когда дочерняя программа завершит выполнение, что делает communicate или wait.
      >>Но при выводе с процесса который работает в данный момент, виснуть всё таки будет.
      ну виснет наверное не то слово, метод read() считывает все до тех пор пока не получит EOF, при работающей программе он его не получит. Следовательно, он будет считывать вывод до тех пор пока не получит EOF.

      Удалить
  2. Ответы
    1. спасибо=)До сих пор не знал о таком. Несколько напрягает указание о возможном возврате пустой строки, но думаю этот вариант выжмет максимум из возможного:
      subprocess.Popen([sys.executable or 'python']+sys.argv+['--windows-daemon'] ...

      Удалить