Как остановить Python от распространения сигналов на подпроцессы? - PullRequest
20 голосов
/ 25 сентября 2010

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

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

Мой код обрабатывает другой сигнал. Ctrl + C остановит симуляцию, спросит, хочу ли я сохранить, и выйду изящно. У меня есть другие обработчики сигналов (например, для принудительного вывода данных).

Я хочу послать сигнал (SIGINT, Ctrl + C) в мой скрипт на python, который спросит пользователя, какой сигнал он хочет отправить в программу.

Единственное, что мешает коду работать, это то, что, как бы я ни делал, Ctrl + C будет «перенаправлен» в подпроцесс: код перехватит его и завершится:

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

Так что, что бы я ни делал, программа получает SIGINT, которую я хочу видеть только в своем скрипте на python. Как я могу это сделать ???

Я тоже пробовал:

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

для запуска программы, но это дает тот же результат: программа ловит SIGINT.

Thanx!

Ответы [ 5 ]

18 голосов
/ 27 марта 2011

Объединение некоторых других ответов, которые помогут, - сигнал, отправленный основному приложению, не будет перенаправлен в подпроцесс.

import os
from subprocess import Popen

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

Popen('whatever', preexec_fn = preexec)
7 голосов
/ 25 сентября 2010

Это действительно можно сделать, используя ctypes. Я бы не стал рекомендовать это решение, но мне было интересно его приготовить, поэтому я решил поделиться им.

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)

Обратите внимание, что это делает множество предположений о различных структурах libc и, как таковое, вероятно, довольно хрупко. При запуске вы не увидите сообщение «SIGINT from child!» распечатаны. Однако, если вы закомментируете звонок на sigprocmask, вы это сделаете. Кажется, чтобы сделать работу:)

4 голосов
/ 25 сентября 2010

POSIX говорит, что программа, запущенная с execvp (это то, что использует subprocess.Popen) должна наследовать маску сигналов вызывающего процесса.

Я могу ошибаться, но я не думаю, что вызов signal изменяет маску. Вы хотите sigprocmask, который Python не предоставляет напрямую.

Это был бы хак, но вы могли бы попытаться установить его через прямой вызов libc через ctypes . Мне определенно было бы интересно узнать лучший ответ по этой стратегии.

Альтернативная стратегия - опросить стандартный ввод для пользовательского ввода как часть вашего основного цикла. («Нажмите Q для выхода / паузы» - что-то в этом роде.) Это обходит проблему обработки сигналов.

2 голосов
/ 02 февраля 2011

Я решил эту проблему, создав вспомогательное приложение, которое я вызываю, вместо того, чтобы создавать дочерний элемент напрямую. Этот помощник меняет свою родительскую группу и затем порождает реальный дочерний процесс.

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)

Я вызываю этого помощника в родительском элементе, передавая в качестве аргументов реальный дочерний элемент и его параметры:

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

Я должен сделать это, потому что мое дочернее приложение не находится под моим контролем, если бы я мог добавить туда setpgrp и вообще обойти помощника.

0 голосов
/ 05 мая 2015

Функция:

os.setpgrp()

Хорошо работает, только если сразу после этого звонят Попену. Если вы пытаетесь предотвратить распространение сигналов на подпроцессы произвольного пакета, тогда пакет может переопределить это, прежде чем создавать подпроцессы, вызывающие распространение сигналов в любом случае. Это тот случай, когда, например, попытка предотвратить распространение сигнала в процессы веб-браузера порождается из пакета Selenium.

Эта функция также устраняет возможность легко обмениваться данными между разделенными процессами без чего-либо вроде сокетов.

Для моих целей это казалось излишним. Вместо того, чтобы беспокоиться о распространении сигналов, я использовал собственный сигнал SIGUSR1. Многие пакеты Python игнорируют SIGUSR1, поэтому, даже если он отправляется всем подпроцессам, он обычно игнорируется

Его можно отправить процессу в bash в Ubuntu с помощью

kill -10 <pid>

Это может быть распознано в вашем коде через

signal.signal(signal.SIGUSR1, callback_function)

Доступные номера сигналов в Ubuntu можно найти по адресу / usr / include / asm / signal.h .

...