2011-12-08 12 views
8

Estoy intentando crear un demonio Python que inicie otros procesos totalmente independientes.Desove indefinido de procesos daemonizados en Python

La idea general es para un comando de shell dado, realice sondeos cada pocos segundos y asegúrese de que se ejecutan exactamente k instancias del comando. Guardamos un directorio de archivos pidfiles, y cuando sondeamos eliminamos los archivos pid cuyos pids ya no se ejecutan y se inician (y hacen los archivos pidf) para todos los procesos que necesitamos para obtener k de ellos.

Los procesos secundarios también deben ser totalmente independientes, de modo que si el proceso principal fallece, los hijos no serán eliminados. Por lo que he leído, parece que no hay forma de hacerlo con el módulo subprocess. Con este fin, he usado el fragmento mencionado aquí:

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

Hice un par modificaciones necesarias (verá las líneas comentadas en el fragmento adjunta):

  1. El principal original proceso no puede salir porque necesitamos que el daemon iniciador perdure indefinidamente.
  2. Los procesos secundarios deben comenzar con el mismo elemento secundario que el primario.

Aquí es mi engendro fn y una prueba:

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 

En esta situación, las cosas parecen funcionar bien, y los procesos secundarios persisten incluso cuando el padre se mató. Sin embargo, todavía estoy corriendo en un límite de generación en el padre original. Después de ~ 650 desova (no al mismo tiempo, los niños han terminado) el proceso padre se ahoga con el error:

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

¿Hay alguna manera de volver a escribir mi función de desove para que pueda generar estos procesos secundarios independientes indefinidamente? ¡Gracias!

+0

¿Cómo es la tabla de proceso? ¿'Ps aux' muestra una gigantesca pila de procesos zombies esperando ser cosechados? (No veo ningún código aquí para 'wait()' en los primeros bifurcados). – sarnold

+0

Creo que sí: http://pastebin.com/qDrFmHWk –

+0

Considere pyinotify para supervisar los cambios en un directorio en su lugar de votación. – aitchnyu

Respuesta

5

Gracias a your list of processes estoy dispuesto a decir que esto se debe a que usted ha golpeado una de una serie de limitaciones fundamentales:

  • rlimit nproc número máximo de procesos de un usuario dado se permite ejecutar - vea setrlimit(2), bash(1)ulimit incorporado, y /etc/security/limits.conf para detalles sobre los límites del proceso por usuario.
  • rlimit nofile número máximo de descriptores de archivos que un proceso determinado puede tener abiertos de una vez. (Cada nuevo proceso probablemente crea tres nuevas tuberías en el padres, por stdin, stdout y stderr descriptores del niño.)
  • de todo el sistema número máximo de procesos; ver /proc/sys/kernel/pid_max.
  • Número máximo de archivos abiertos en todo el sistema; ver /proc/sys/fs/file-max.

Como no está cosechando a sus hijos muertos, muchos de estos recursos se mantienen abiertos más de lo debido. Su segundo los niños están siendo manejados correctamente por init(8) - su padre está muerto, por lo que se vuelven a parear a init(8), y init(8) se limpiará después de ellos (wait(2)) cuando mueren.

Sin embargo, su programa es responsable de la limpieza después del primer conjunto de de niños. Los programas C normalmente instalan un controlador signal(7) para SIGCHLD que llama al wait(2) o waitpid(2) para obtener el estado de salida de los niños y así eliminar sus entradas de la memoria del kernel.

Pero el manejo de señales en un script es un poco molesto. Si puede establecer la disposición de señal SIGCHLD explícitamente en SIG_IGN, el kernel sabrá que no está interesado en el estado de salida y obtendrá los elementos secundarios para usted_.

Trate de añadir:

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

cerca de la parte superior de su programa.

Tenga en cuenta que no sé lo que esto hace por Subprocess. Puede que no esté satisfecho. Si ese es el caso, entonces necesitará install a signal handler para llamar al wait(2) por usted.

+1

El subproceso supone manejar magia SIGCHLD. Combinado con close_fds, debería resolver el error en algunas versiones de python (ver http://bugs.python.org/issue4216). –

+0

¡La configuración de la señal y close_fds me lo resolvió en OSX y Ubuntu! Hizo 50k procesos fácilmente. ¡Gracias a los dos! –

+0

@ILYA: Si 'Subprocess' se usara para crear _todos los procesos, probablemente hubiera funcionado bien; pero la mitad de los procesos se crean a mano en este caso. – sarnold

3

He modificado ligeramente su código y he podido ejecutar 5000 procesos sin ningún problema. Así que estoy de acuerdo con @sarnold en que alcanzas alguna limitación fundamental. Mis modificaciones son:

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) 
+0

cambió a: 'pid = subprocess.Popen (CMD, cwd = child_cwd, cáscara = True, close_fds = True) .pid' pero todavía no se pudo: ' desove 647 ... tenedor # 2 failed : 35 (Recurso no disponible temporalmente) desove 648 ... fork # 1 failed: 35 (Recurso temporalmente no disponible) ' –

+0

close_fds junto con la configuración de la señal funcionó perfectamente para mí. –

Cuestiones relacionadas