Python: как запретить подпроцессам получать CTRL-C / Control-C / SIGINT

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

Выделенному серверу необходимо явно дать команду на корректное завершение работы. Таким образом, CTRL-C не должен достигать процесса сервера.

Если я перехватываю исключение KeyboardInterrupt или перезаписываю обработчик SIGINT в python, серверный процесс все равно получает CTRL-C и немедленно останавливается.

Итак, мой вопрос: как запретить подпроцессам получать CTRL-C / Control-C / SIGINT?

4 ответа

Решение

Кто-то из IRC-канала #python (Freenode) помог мне, указав на параметр preexec_fn в subprocess.Popen (...):

Если preexec_fn установлен для вызываемого объекта, этот объект будет вызываться в дочернем процессе непосредственно перед выполнением дочернего процесса. (Только для Unix)

Таким образом, следующий код решает проблему (только для UNIX):

import subprocess
import signal

def preexec_function():
    # Ignore the SIGINT signal by setting the handler to the standard
    # signal handler SIG_IGN.
    signal.signal(signal.SIGINT, signal.SIG_IGN)

my_process = subprocess.Popen(
    ["my_executable"],
    preexec_fn = preexec_function
)

Примечание. Сигналу на самом деле не мешают достичь подпроцесса. Вместо этого preexec_fn выше перезаписывает обработчик сигнала по умолчанию, так что сигнал игнорируется. Таким образом, это решение может не работать, если подпроцесс снова перезаписывает обработчик SIGINT.

Еще одно примечание: это решение работает для всех видов подпроцессов, то есть оно не ограничивается подпроцессами, написанными также на Python. Например, выделенный сервер, для которого я пишу свою оболочку, фактически написан на Java.

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

import os
from subprocess import Popen

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

Popen('whatever', preexec_fn = preexec)

Вы можете сделать что-то вроде этого, чтобы заставить его работать в Windows и Unix:

import subprocess
import sys

def pre_exec():
    # To ignore CTRL+C signal in the new process
    signal.signal(signal.SIGINT, signal.SIG_IGN)

if sys.platform.startswith('win'):
    #https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
    #CREATE_NEW_PROCESS_GROUP=0x00000200 -> If this flag is specified, CTRL+C signals will be disabled
    my_sub_process=subprocess.Popen(["executable"], creationflags=0x00000200)
else:
    my_sub_process=subprocess.Popen(["executable"], preexec_fn = pre_exec)

После часа различных попыток это сработало для меня:

process = subprocess.Popen(["someprocess"], creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP)

Это решение для окон.

Попробуйте настроить SIGINT так, чтобы она игнорировалась перед порождением подпроцесса (после этого сбросьте его на поведение по умолчанию).

Если это не сработает, вам нужно прочитать об управлении заданиями и узнать, как поместить процесс в его собственную группу фоновых процессов, чтобы ^C даже не заставляло ядро ​​посылать ему сигнал в первое место. (Может быть невозможным в Python без написания помощников на языке C.)

Смотрите также этот старый вопрос.

Другие вопросы по тегам