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

时间:2010-09-24 22:12:10

标签: python subprocess signals

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

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

我的代码处理不同的信号。 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)
    [...]

所以无论我做什么,程序都会收到我只希望我的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)

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

感谢名单!

5 个答案:

答案 0 :(得分:18)

结合其他一些可以解决问题的答案 - 发送到主应用程序的信号不会转发到子流程。

import os
from subprocess import Popen

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

Popen('whatever', preexec_fn = preexec)

答案 1 :(得分: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)

<强> 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!”打印。但是,如果您将通话注释到sigprocmask,那么您会。似乎做了这个工作:)

答案 2 :(得分:4)

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

我可能错了,但我认为调用signal不会修改掩码。你想要sigprocmask,哪个python没有直接暴露。

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

替代策略是将stdin作为主循环的一部分来轮询用户输入。 (“按Q退出/暂停” - 就像那样。)这样可以回避处理信号的问题。

答案 3 :(得分:2)

我通过创建一个我调用的帮助应用程序而不是直接创建子进程来解决这个问题。此帮助程序更改其父组,然后生成真正的子进程。

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并完全绕过帮助程序。

答案 4 :(得分:0)

功能:

os.setpgrp()

只有在Popen被随后调用后才能正常工作。如果您试图阻止信号传播到任意包的子进程,则包可能会在创建子进程之前覆盖此信息,从而导致信号无论如何都要传播。例如,当试图阻止信号传播到Selenium包中产生的Web浏览器进程时就是这种情况。

此功能还消除了在没有套接字之类的情况下在分离的进程之间轻松通信的能力。

就我的目的而言,这似乎有点矫枉过正。我没有担心信号传播,而是使用自定义信号SIGUSR1。许多Python包忽略了SIGUSR1,所以即使它被发送到所有子进程,它也会被忽略

可以使用

将其发送到Ubuntu上的bash进程
kill -10 <pid>

可以通过

在代码中识别
signal.signal(signal.SIGUSR1, callback_function)

Ubuntu上的可用信号编号可在 /usr/include/asm/signal.h 找到。