如何阻止python将信号传播到子进程?

big*_*gie 22 python subprocess signals

我正在使用python来管理一些模拟.我构建参数并使用以下命令运行程序:

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

我的代码处理不同的信号.Ctrl + C将停止模拟,询问我是否要保存,然后正常退出.我有其他信号处理程序(例如强制数据输出).

我想要的是向我的python脚本发送一个信号(SIGINT,Ctrl + C),它将询问用户他想要发送给程序的信号.

阻止代码工作的唯一因素是,无论我做什么,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)
    [...]
Run Code Online (Sandbox Code Playgroud)

所以无论我做什么,程序都会收到我只希望我的python脚本看到的SIGINT.我怎样才能做到这一点???

我也尝试过:

signal.signal(signal.SIGINT, signal.SIG_IGN)
pid = subprocess.Popen(shlex.split(command), stdout=pipe, stderr=pipe)
signal.signal(signal.SIGINT, signal.SIG_DFL)
Run Code Online (Sandbox Code Playgroud)

运行程序,但这会得到相同的结果:程序捕获SIGINT.

感谢名单!

Mar*_*ota 18

结合其他一些可以解决问题的答案 - 没有信号发送到主应用程序将被转发到子进程.

import os
from subprocess import Popen

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

Popen('whatever', preexec_fn = preexec)
Run Code Online (Sandbox Code Playgroud)

  • 如果你使用Python 2.x并且**不再使用`preexec_fn`**,请考虑使用[subprocess32] [1]而不是subprocess.这不安全.请改用新的Popen**start_new_session = True**参数.[1]:http://code.google.com/p/python-subprocess32/ (13认同)
  • 除了安全参数之外,上面的代码也可以工作,因为通常当您按Ctrl-C时,SIGINT会被发送到进程组.默认情况下,所有子进程都与父进程位于同一进程组中.通过调用`setpgrp()`,您可以将子进程放到一个新的进程组中,这样它就不会从父进程获取信号. (5认同)
  • 在风格方面,它更像Pythonic:'Popen('what',preexec_fn = os.setpgrp)`.没有必要定义preexec(). (3认同)

Mic*_*ior 7

这确实可以使用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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

请注意,这会对各种libc结构做出一系列假设,因此可能非常脆弱.运行时,您将看不到消息"来自孩子的SIGINT!" 打印.但是,如果您将通话注释掉sigprocmask,那么您将会.似乎做的工作:)


bst*_*rre 4

POSIX 表示使用execvp(这是 subprocess.Popen 使用的)运行的程序应该继承调用进程的信号掩码。

我可能是错的,但我不认为调用signal会修改掩码。你想要的sigprocmask,python不直接公开。

这将是一个 hack,但您可以尝试通过ctypes直接调用 libc 来设置它。我肯定有兴趣看到有关此策略的更好答案。

另一种策略是在主循环中轮询标准输入以获取用户输入。(“按 Q 退出/暂停”——类似的东西。)这回避了处理信号的问题。