2010-09-24 12 views
18

Używam Pythona do zarządzania niektórymi symulacjami. Buduję parametry i uruchamiam program, używając:Jak zatrzymać Pythona od propagacji sygnałów do podprocesów?

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

Mój kod obsługuje inny sygnał. Ctrl + C przerwie symulację, zapyta, czy chcę zapisać, i wyjdę z wdziękiem. Mam inne procedury obsługi sygnału (np. W celu wymuszenia danych wyjściowych).

Chcę wysłać sygnał (SIGINT, Ctrl + C) do mojego skryptu python, który zapyta użytkownika, jaki sygnał chce wysłać do programu.

Jedyna rzecz zapobiegania kod do pracy jest to, że wydaje się, że cokolwiek robię, Ctrl + C będzie „przekazany” do podproces: kod złapie go i wyjście:

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

Tak cokolwiek robię, program otrzymuje SIGINT, który chcę tylko, aby mój skrypt python mógł zobaczyć. Jak mogę to zrobić???

Próbowałem również:

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

aby uruchomić program, ale daje ten sam wynik: program łapie SIGINT.

Thanx!

Odpowiedz

4

POSIX mówi, że program uruchamiany z execvp (który jest używany w podprocesie.Popen) powinien dziedziczyć maskę sygnału procesu wywołującego.

Mogę się mylić, ale nie sądzę, że wywołanie signal modyfikuje maskę. Chcesz sigprocmask, którego python nie ujawnia się bezpośrednio.

To byłby hack, ale można spróbować ustawić go poprzez bezpośrednie połączenie z libc poprzez ctypes. Zdecydowanie byłbym zainteresowany lepszą odpowiedzią na temat tej strategii.

Alternatywną strategią byłoby odpytywanie stdin na wejście użytkownika jako część głównej pętli. ("Naciśnij przycisk Q, aby wyjść/wstrzymać" - coś w tym stylu.) Powoduje to problem z obsługą sygnałów.

7

Można to zrobić za pomocą ctypes. Naprawdę nie poleciłbym tego rozwiązania, ale byłem wystarczająco zainteresowany, żeby coś ugotować, więc pomyślałem, że to zrobię.

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) 

Zauważ, że to sprawia, że ​​kilka założeń dotyczących różnych struktur libc i jako takie, jest chyba dość kruche. Po uruchomieniu nie zobaczysz komunikatu "SIGINT from child!" wydrukowane. Jeśli jednak skomentujesz połączenie z numerem sigprocmask, to zrobisz to. Wydaje się, że wykonuję zadanie :)

+0

Dzięki za Twoją sugestię. Myślę, że to zbyt skomplikowany cel, jaki mam. Zasadniczo chcę tylko wstrzymać skrypt nadrzędny, zapytać użytkownika o coś i wysłać sygnał do wszystkich procesów podrzędnych. Może kolejne naciśnięcie klawisza może zatrzymać skrypt nadrzędny, na przykład ctrl + x? –

+0

Tak, jak już powiedziałem, naprawdę nie poleciłbym tego rozwiązania. Możesz użyć dowolnej kombinacji klawiszy, która ma zostać zatrzymana, jeśli chcesz odsłuchać zdarzenia na klawiaturze w osobnym wątku. –

2

Rozwiązałem ten problem, tworząc aplikację pomocniczą, którą wywołuję, zamiast bezpośrednio tworzyć dziecko. Ten pomocnik zmienia swoją grupę nadrzędną, a następnie odradza prawdziwy proces potomny.

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) 

nazywam tego pomocnika w rodzica, przekazując prawdziwe dziecko i jego parametry jako argumenty

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

muszę to zrobić, ponieważ moja aplikacja dziecko nie jest pod moją kontrolą, gdyby było Mogłem dodać tam setpgrp i całkowicie ominąć pomocnika.

17

Łączenie niektórych innych odpowiedzi, że załatwi - brak sygnału przesyłane do głównej aplikacji zostaną przekazane do podproces.

import os 
from subprocess import Popen 

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

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

Proszę rozważyć użycie [subprocess32] [1] zamiast podprocesu, jeśli używasz Pythona 2.x i ** nie stosuj już 'preexec_fn' **. To nie jest bezpieczne. Zamiast tego użyj nowego parametru Popen ** start_new_session = True **. [1]: http://code.google.com/p/python-subprocess32/ – gps

+0

To jest dokładnie to, czego szukałem. Dzięki. –

+4

Oprócz argumentu bezpieczeństwa, powyższy kod działa, ponieważ normalnie po naciśnięciu Ctrl-C SIGINT jest wysyłany do grupy procesów. Domyślnie wszystkie podprocesy znajdują się w tej samej grupie procesów, co proces nadrzędny. Wywołując 'setpgrp()' wprowadzasz proces potomny do nowej grupy procesów, więc nie otrzyma sygnałów od rodzica. –

0

Funkcja:

os.setpgrp() 

działa dobrze tylko wtedy, gdy Popen jest wywoływana tuż po jej zakończeniu. Jeśli próbujesz zapobiec propagacji sygnałów do podprocesów dowolnego pakietu, pakiet może to zmienić przed utworzeniem podprocesów powodujących, że sygnały będą propagowane w każdym razie. Dzieje się tak na przykład wtedy, gdy próbujemy zapobiec propagacji sygnału w procesach przeglądarki internetowej, które powstały z pakietu Selenium.

Funkcja usuwa także zdolność komunikowania się z łatwością pomiędzy oddzielonymi procesach bez coś w gniazdach.

Dla moich celów, to wydawało się przesadą. Zamiast martwić się o propagowanie sygnałów, użyłem niestandardowego sygnału SIGUSR1. Wiele pakietów Pythona ignorować SIGUSR1, więc nawet jeśli jest on wysyłany do wszystkich podprocesów, to zazwyczaj są ignorowane

Może być wysłany do procesu, w bash na Ubuntu używając

kill -10 <pid> 

mogą być ujmowane w swojej Kod poprzez

signal.signal(signal.SIGUSR1, callback_function) 

dostępne numery sygnałów na Ubuntu można znaleźć na /usr/include/asm/signal.h.

Powiązane problemy