Неопределенный демонизированный процесс, возникающий в Python - PullRequest
8 голосов
/ 08 декабря 2011

Я пытаюсь создать демон Python, который запускает другие полностью независимые процессы.

Общая идея для данной команды оболочки - опросить каждые несколько секунд и убедиться, что запущено ровно k экземпляров команды. Мы храним каталог pid-файлов, и когда мы проводим опрос, мы удаляем pid-файлы, pid-файлы которых больше не запускаются и не запускаются (и создают pid-файлы), однако для многих процессов нам нужно получить k из них.

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

http://code.activestate.com/recipes/66012-fork-a-daemon-process-on-unix/

Я сделал пару необходимых изменений (вы увидите закомментированные строки в прикрепленном фрагменте):

  1. Исходный родительский процесс не может завершиться, потому что нам нужно, чтобы демон запуска работал бесконечно долго.
  2. Дочерние процессы должны начинаться с того же cwd, что и родительский.

Вот мой spn fn и тест:

import os
import sys
import subprocess
import time

def spawn(cmd, child_cwd):
    """
    do the UNIX double-fork magic, see Stevens' "Advanced 
    Programming in the UNIX Environment" for details (ISBN 0201563177)
    http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
    """
    try: 
        pid = os.fork() 
        if pid > 0:
            # exit first parent
            #sys.exit(0) # parent daemon needs to stay alive to launch more in the future
            return
    except OSError, e: 
        sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
        sys.exit(1)

    # decouple from parent environment
    #os.chdir("/") # we want the children processes to 
    os.setsid() 
    os.umask(0) 

    # do second fork
    try: 
        pid = os.fork() 
        if pid > 0:
            # exit from second parent
            sys.exit(0) 
    except OSError, e: 
        sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
        sys.exit(1) 

    # redirect standard file descriptors
    sys.stdout.flush()
    sys.stderr.flush()
    si = file('/dev/null', 'r')
    so = file('/dev/null', 'a+')
    se = file('/dev/null', 'a+', 0)
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())

    pid = subprocess.Popen(cmd, cwd=child_cwd, shell=True).pid

    # write pidfile       
    with open('pids/%s.pid' % pid, 'w') as f: f.write(str(pid))
    sys.exit(1)

def mkdir_if_none(path):
    if not os.access(path, os.R_OK):
        os.mkdir(path)

if __name__ == '__main__':
    try:
        cmd = sys.argv[1]
        num = int(sys.argv[2])
    except:
        print 'Usage: %s <cmd> <num procs>' % __file__
        sys.exit(1)
    mkdir_if_none('pids')
    mkdir_if_none('test_cwd')

    for i in xrange(num):
        print 'spawning %d...'%i
        spawn(cmd, 'test_cwd')
        time.sleep(0.01) # give the system some breathing room

В этой ситуации кажется, что все работает нормально, и дочерние процессы продолжаются, даже когда родитель убит. Тем не менее, я все еще сталкиваюсь с лимитом появления для исходного родителя. После того, как ~ 650 порождает (не одновременно, дети закончили), родительский процесс задыхается с ошибкой:

spawning 650...
fork #2 failed: 35 (Resource temporarily unavailable)

Есть ли способ переписать мою функцию порождения, чтобы я мог бесконечно порождать эти независимые дочерние процессы? Спасибо!

Ответы [ 2 ]

5 голосов
/ 08 декабря 2011

Благодаря вашему списку процессов Я хочу сказать, что это потому, что вы достигли одного из ряда фундаментальных ограничений:

  • rlimit nproc максимумколичество процессов, которые может выполнять данный пользователь - см. setrlimit(2), bash(1) ulimit встроенный и /etc/security/limits.conf для получения подробных сведений об ограничениях процессов для пользователя.
  • rlimit nofile максимальное количество файловых дескрипторов, которые разрешено открывать данному процессу одновременно.(Каждый новый процесс, вероятно, создает три новых канала в родительском для дочерних дескрипторов stdin, stdout и stderr.)
  • Максимальное количество процессов в системе;см. /proc/sys/kernel/pid_max.
  • Максимальное количество открытых файлов в системе;см. /proc/sys/fs/file-max.

Поскольку вы не пожинаете своих мертвых детей, многие из этих ресурсов открыты дольше, чем следовало бы.Ваши вторые дети должным образом обрабатываются init(8) - их родители мертвы, поэтому они переизбираются на init(8), и init(8) будет убирать за ними (wait(2)), когдаони умирают.

Однако ваша программа отвечает за уборку после первого набора детей.Программы на Си обычно устанавливают обработчик signal(7) для SIGCHLD, который вызывает wait(2) или waitpid(2), чтобы получить статус выхода детей и, таким образом, удалить его записи из памяти ядра.

Но обработка сигналов в скриптенемного раздражает.Если вы можете явно установить расположение сигнала SIGCHLD на SIG_IGN, ядро ​​узнает, что вас не интересует состояние выхода, и получит для вас дочерние элементы _.

Попробуйте добавить:

import signal
signal.signal(signal.SIGCHLD, signal.SIG_IGN)

в верхней части вашей программы.

Обратите внимание, что я не знаю, что это делает для Subprocess.Это может быть не приятно.В таком случае вам нужно установить обработчик сигнала , чтобы позвонить вам wait(2).

3 голосов
/ 08 декабря 2011

Я немного изменил ваш код и смог без проблем запустить 5000 процессов. Поэтому я согласен с @sarnold, что вы столкнулись с некоторыми фундаментальными ограничениями. Мои модификации:

proc = subprocess.Popen(cmd, cwd=child_cwd, shell=True, close_fds=True)    
pid = proc.pid

# write pidfile       
with open('pids/%s.pid' % pid, 'w') as f: f.write(str(pid))
proc.wait()
sys.exit(1)
...