2010-09-24 5 views
18

Sto usando python per gestire alcune simulazioni. Costruisco i parametri ed eseguo il programma usando:Come impedire a Python di propagare i segnali ai sottoprocessi?

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

Il mio codice gestisce un segnale diverso. Ctrl + C interromperà la simulazione, chiederà se voglio salvare ed esco con grazia. Ho altri gestori di segnale (per forzare l'output dei dati, ad esempio).

Quello che voglio è inviare un segnale (SIGINT, Ctrl + C) al mio script python che chiederà all'utente quale segnale vuole inviare al programma.

L'unica cosa che impedisce il codice per lavoro è che sembra che tutto quello che faccio, Ctrl + C sarà "inoltrata" al sottoprocesso: il codice sarà prenderlo e l'uscita:

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) 
    [...] 

Così qualunque cosa faccia, il programma sta ricevendo il SIGINT che voglio solo vedere dal mio script python. Come lo posso fare???

Ho anche provato:

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

per eseguire il programma, ma questo dà lo stesso risultato: il programma cattura l'SIGINT.

Grazie!

risposta

4

POSIX dice che un programma eseguito con execvp (che è ciò che subprocess.Popen utilizza) dovrebbe ereditare la maschera del segnale del processo chiamante.

potrei sbagliarmi, ma non credo che chiamare signal modifica la maschera. Si desidera sigprocmask, che python non espone direttamente.

sarebbe un hack, ma si potrebbe provare a impostare tramite una chiamata diretta a libc via ctypes. Sarei sicuramente interessato a vedere una risposta migliore su questa strategia.

La strategia alternativa potrebbe essere quella di polling stdin per l'input dell'utente come parte del vostro ciclo principale. ("Premi Q per uscire/mettere in pausa" - qualcosa del genere.) Questo elude il problema della gestione dei segnali.

7

Questa può infatti essere fatto utilizzando ctypes. Non consiglierei veramente questa soluzione, ma ero abbastanza interessato a cucinare qualcosa, quindi ho pensato di condividerlo.

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) 

Si noti che questo fa un po 'di ipotesi su varie strutture libc e come tale, è probabilmente abbastanza fragile. Durante l'esecuzione, non vedrai il messaggio "SIGINT da bambino!" stampato. Tuttavia, se commentate la chiamata a sigprocmask, allora lo farete. Sembra di fare il lavoro :)

+0

Grazie per il tuo suggerimento. Penso che sia troppo complicato per l'obiettivo che ho però. Fondamentalmente, voglio solo mettere in pausa lo script genitore, chiedere all'utente qualcosa e inviare un segnale a tutti i processi figli. Forse un altro input da tastiera potrebbe mettere in pausa lo script genitore, come ctrl + x? –

+0

Sì, come ho detto, non raccomanderei davvero la soluzione. È possibile utilizzare qualsiasi combinazione di tasti che si desidera mettere in pausa il genitore se si ascoltano eventi di tastiera in un thread separato. –

2

Ho risolto questo problema con la creazione di un'applicazione di supporto che io chiamo invece di creare direttamente il bambino. Questo helper cambia il suo gruppo genitore e quindi genera il vero processo figlio.

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) 

mi chiamare questo helper nel genitore, passando il bambino reale e suoi parametri come argomenti:

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

devo fare questo perché il mio bambino app non è sotto il mio controllo, se fosse Avrei potuto aggiungere il setpgrp lì e scavalcato l'helper del tutto.

17

Combinando alcune delle altre risposte che faranno il trucco - nessun segnale inviato all'app principale verrà inoltrato al sottoprocesso.

import os 
from subprocess import Popen 

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

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

Si prega di considerare l'utilizzo di [subprocess32] [1] invece di subprocess se si utilizza Python 2.xe ** non si usa più 'preexec_fn' **. Non è sicuro Utilizza invece il nuovo parametro Popen ** start_new_session = True **. [1]: http://code.google.com/p/python-subprocess32/ – gps

+0

Questo è esattamente quello che stavo cercando. Grazie. –

+4

Oltre all'argomento relativo alla sicurezza, il codice sopra riportato funziona perché normalmente quando si preme Ctrl-C il SIGINT viene inviato al gruppo di processi. Per impostazione predefinita, tutti i sottoprocessi si trovano nello stesso gruppo di processi con processo padre. Chiamando 'setpgrp()' si mette il processo figlio in un nuovo gruppo di processi in modo che non ottenga i segnali dal genitore. –

0

La funzione:

os.setpgrp() 

funziona bene solo se Popen viene chiamato subito dopo. Se si sta tentando di impedire la propagazione dei segnali ai sottoprocessi di un pacchetto arbitrario, il pacchetto può sovrascriverlo prima di creare sottoprocessi che causano comunque la propagazione dei segnali. Questo è il caso quando, ad esempio, si tenta di impedire la propagazione del segnale nei processi del browser Web generati dal pacchetto Selenium.

Questa funzione rimuove anche la capacità di comunicare facilmente tra i processi separati senza qualcosa di simile a socket.

Per i miei scopi, questo sembrava eccessivo. Invece di preoccuparmi dei segnali che si propagavano, ho usato il segnale personalizzato SIGUSR1. Molti pacchetti Python ignorano SIGUSR1, quindi, anche se viene inviato a tutti i sottoprocessi, di solito ignorati

Può essere inviato a un processo in bash su Ubuntu usando

kill -10 <pid> 

Può essere riconosciuto nella vostra codice tramite

signal.signal(signal.SIGUSR1, callback_function) 

I numeri di segnale disponibili su Ubuntu sono disponibili all'indirizzo /usr/include/asm/signal.h.