2010-09-24 14 views
18

Estoy usando Python para administrar algunas simulaciones. Construyo los parámetros y ejecuto el programa usando:¿Cómo evitar que python propague señales a subprocesos?

pipe = open('/dev/null', 'w') 
pid = subprocess.Popen(shlex.split(command), stdout=pipe, stderr=pipe) 

Mi código maneja una señal diferente. Ctrl + C detendrá la simulación, preguntará si quiero guardar y saldrá con elegancia. Tengo otros manejadores de señal (para forzar la salida de datos, por ejemplo).

Lo que quiero es enviar una señal (SIGINT, Ctrl + C) a mi script python que le preguntará al usuario qué señal desea enviar al programa.

El único que impide que el código funcione es que parece que todo lo que hago, Ctrl + C será "remitido" al subproceso: el código lo cogerá y salida:

try: 
    <wait for available slots> 
except KeyboardInterrupt: 
    print "KeyboardInterrupt catched! All simulations are paused. Please choose the signal to send:" 
    print " 0: SIGCONT (Continue simulation)" 
    print " 1: SIGINT (Exit and save)" 
    [...] 
    answer = raw_input() 
    pid.send_signal(signal.SIGCONT) 
    if (answer == "0"): 
    print " --> Continuing simulation..." 
    elif (answer == "1"): 
    print " --> Exit and save." 
    pid.send_signal(signal.SIGINT) 
    [...] 

Así haga lo que haga, el programa recibe el SIGINT que solo quiero que vea mi script de python. ¿¿¿Cómo puedo hacer eso???

También probé:

signal.signal(signal.SIGINT, signal.SIG_IGN) 
pid = subprocess.Popen(shlex.split(command), stdout=pipe, stderr=pipe) 
signal.signal(signal.SIGINT, signal.SIG_DFL) 

para ejecutar el programa, pero esto le da el mismo resultado: el programa capta la SIGINT.

Gracias!

Respuesta

4

POSIX dice que un programa ejecutado con execvp (que es lo que subprocess.Popen usa) debe heredar la máscara de señal del proceso de llamada.

Podría estar equivocado, pero no creo que llamar a signal modifique la máscara. Desea sigprocmask, que python no expone directamente.

Sería un truco, pero podría intentar configurarlo mediante una llamada directa a libc a través del ctypes. Definitivamente estaría interesado en ver una mejor respuesta sobre esta estrategia.

La estrategia alternativa sería sondear stdin para la entrada del usuario como parte de su bucle principal. ("Presione Q para salir/pausar" - algo así.) Esto elude el problema del manejo de señales.

7

Esto realmente se puede hacer usando ctypes. Realmente no recomendaría esta solución, pero estaba lo suficientemente interesado como para cocinar algo, así que pensé que podría compartirlo.

parent.py

#!/usr/bin/python 

from ctypes import * 
import signal 
import subprocess 
import sys 
import time 

# Get the size of the array used to 
# represent the signal mask 
SIGSET_NWORDS = 1024/(8 * sizeof(c_ulong)) 

# Define the sigset_t structure 
class SIGSET(Structure): 
    _fields_ = [ 
     ('val', c_ulong * SIGSET_NWORDS) 
    ] 

# Create a new sigset_t to mask out SIGINT 
sigs = (c_ulong * SIGSET_NWORDS)() 
sigs[0] = 2 ** (signal.SIGINT - 1) 
mask = SIGSET(sigs) 

libc = CDLL('libc.so.6') 

def handle(sig, _): 
    if sig == signal.SIGINT: 
     print("SIGINT from parent!") 

def disable_sig(): 
    '''Mask the SIGINT in the child process''' 
    SIG_BLOCK = 0 
    libc.sigprocmask(SIG_BLOCK, pointer(mask), 0) 

# Set up the parent's signal handler 
signal.signal(signal.SIGINT, handle) 

# Call the child process 
pid = subprocess.Popen("./child.py", stdout=sys.stdout, stderr=sys.stdin, preexec_fn=disable_sig) 

while (1): 
    time.sleep(1) 

child.py

#!/usr/bin/python 
import time 
import signal 

def handle(sig, _): 
    if sig == signal.SIGINT: 
     print("SIGINT from child!") 

signal.signal(signal.SIGINT, handle) 
while (1): 
    time.sleep(1) 

Tenga en cuenta que esto hace un montón de suposiciones acerca de las diversas estructuras de libc y, como tal, es probablemente bastante frágil. Cuando se ejecuta, no verá el mensaje "SIGINT from child!" impreso. Sin embargo, si comenta la llamada al sigprocmask, lo hará. Parece que hace el trabajo :)

+0

Gracias por su sugerencia. Creo que es demasiado complicado para el objetivo que tengo. Básicamente, solo quiero pausar el script principal, preguntar algo al usuario y enviar una señal a todos los procesos secundarios. ¿Quizás otra entrada de teclado podría pausar el script principal, como ctrl + x? –

+0

Sí, como dije, realmente no recomendaría la solución. Puede usar cualquier combinación de teclas para pausar al padre si escucha los eventos del teclado en un hilo separado. –

2

Resolví este problema al crear una aplicación de ayuda que llamo en lugar de crear el niño directamente. Este ayudante cambia su grupo principal y luego engendra el proceso hijo real.

import os 
import sys 

from time import sleep 
from subprocess import Popen 

POLL_INTERVAL=2 

# dettach from parent group (no more inherited signals!) 
os.setpgrp() 

app = Popen(sys.argv[1:]) 
while app.poll() is None: 
    sleep(POLL_INTERVAL) 

exit(app.returncode) 

que llamar a este ayudante en el padre, que pasa el niño real y sus parámetros como argumentos:

Popen(["helper", "child", "arg1", ...]) 

que tengo que hacer esto porque mi aplicación niño no está bajo mi control, si fuera Podría haber agregado el setpgrp allí y eludir por completo al ayudante.

17

Combinando algunas de las otras respuestas que harán el truco: ninguna señal enviada a la aplicación principal será reenviada al subproceso.

import os 
from subprocess import Popen 

def preexec(): # Don't forward signals. 
    os.setpgrp() 

Popen('whatever', preexec_fn = preexec) 
+11

Considere usar [subprocess32] [1] en lugar de subprocesar si usa Python 2.x y ** ya no usa 'preexec_fn' **. No es seguro. Use el nuevo parámetro Popen ** start_new_session = True ** en su lugar. [1]: http://code.google.com/p/python-subprocess32/ – gps

+0

Esto es exactamente lo que estaba buscando. Gracias. –

+4

Además del argumento de seguridad, el código anterior funciona porque, normalmente, al presionar Ctrl-C, el SIGINT se envía al grupo de procesos. Por defecto, todos los subprocesos están en el mismo grupo de procesos con el proceso principal. Al llamar a 'setpgrp()', usted coloca el proceso de su hijo en un nuevo grupo de procesos para que no reciba las señales del padre. –

0

La función:

os.setpgrp() 

funciona bien sólo si Popen está siendo llamado justo después. Si está tratando de evitar que las señales se propaguen a los subprocesos de un paquete arbitrario, entonces el paquete puede anular esto antes de crear subprocesos, haciendo que las señales se propaguen de todos modos. Este es el caso cuando, por ejemplo, tratamos de evitar la propagación de la señal en los procesos del navegador web generados a partir del paquete Selenio.

Esta función también elimina la capacidad de comunicarse fácilmente entre los procesos separados sin algo como los sockets.

Para mis propósitos, esto parecía exagerado. En lugar de preocuparme por las señales que se propagan, utilicé la señal personalizada SIGUSR1. Muchos paquetes de Python ignoran SIGUSR1, por lo que incluso si se envía a todos los subprocesos, por lo general serán ignorados

Puede ser enviado a un proceso en bash en Ubuntu usando

kill -10 <pid> 

Se distingue en su código a través de

signal.signal(signal.SIGUSR1, callback_function) 

Los números de señal disponibles en Ubuntu se puede encontrar en /usr/include/asm/signal.h.

Cuestiones relacionadas