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

发布于 2024-09-24 17:16:06 字数 1200 浏览 5 评论 0原文

我正在使用 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)
    [...]

所以无论我做什么,程序都会收到 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。

谢谢!

I'm using python to manage some simulations. I build the parameters and run the program using:

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

My code handles different signal. Ctrl+C will stop the simulation, ask if I want to save, and exit gracefully. I have other signal handlers (to force data output for example).

What I want is to send a signal (SIGINT, Ctrl+C) to my python script which will ask the user which signal he wants to send to the program.

The only thing preventing the code to work is that it seems that whatever I do, Ctrl+C will be "forwarded" to the subprocess: the code will catch it to and exit:

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

So whatever I do, the program is receiving the SIGINT that I only want my python script to see. How can I do that???

I also tried:

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

to run the program but this gives the same result: the program catches the SIGINT.

Thanx!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

大海や 2024-10-01 17:16:06

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

import os
from subprocess import Popen

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

Popen('whatever', preexec_fn = preexec)

Combining some of other answers that will do the trick - no signal sent to main app will be forwarded to the subprocess.

import os
from subprocess import Popen

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

Popen('whatever', preexec_fn = preexec)
分开我的手 2024-10-01 17:16:06

这确实可以使用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 的调用,那么您就可以了。似乎可以完成这项工作:)

This can indeed be done using ctypes. I wouldn't really recommend this solution, but I was interested enough to cook something up, so I thought I would share it.

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)

Note that this makes a bunch of assumptions about various libc structures and as such, is probably quite fragile. When running, you won't see the message "SIGINT from child!" printed. However, if you comment out the call to sigprocmask, then you will. Seems to do the job :)

漆黑的白昼 2024-10-01 17:16:06

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

我可能是错的,但我不认为调用 signal 修改掩码。你想要 sigprocmask,而 python 不需要直接暴露。

这将是一个黑客,但您可以尝试通过 ctypes。我肯定有兴趣看到有关此策略的更好答案。

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

POSIX says that a program run with execvp (which is what subprocess.Popen uses) should inherit the signal mask of the calling process.

I could be wrong, but I don't think calling signal modifies the mask. You want sigprocmask, which python does not directly expose.

It would be a hack, but you could try setting it via a direct call to libc via ctypes. I'd definitely be interested in seeing a better answer on this strategy.

The alternative strategy would be to poll stdin for user input as part of your main loop. ("Press Q to quit/pause" -- something like that.) This sidesteps the issue of handling signals.

自此以后,行同陌路 2024-10-01 17:16:06

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

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 并完全绕过助手。

I resolved this problem by creating a helper app that I call instead of creating the child directly. This helper changes its parent group and then spawn the real child process.

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)

I call this helper in the parent, passing the real child and its parameters as arguments:

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

I have to do this because my child app is not under my control, if it were I could have added the setpgrp there and bypassed the helper altogether.

时间你老了 2024-10-01 17:16:06

该函数:

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

The function:

os.setpgrp()

Works well only if Popen is being called right afterwards. If you are trying to prevent signals from being propagated to the subprocesses of an arbitrary package, then the package may override this before creating subprocesses causing signals to be propagated anyways. This is the case when, for example, trying to prevent signal propagation into web browser processes spawned from the package Selenium.

This function also removes the ability to easily communicate between the separated processes without something like sockets.

For my purposes, this seemed like overkill. Instead of worrying about signals propagating, I used the custom signal SIGUSR1. Many Python packages ignore SIGUSR1, so even if it is sent to all subprocesses, it will usually be ignored

It can be sent to a process in bash on Ubuntu using

kill -10 <pid>

It can be recognized in your code via

signal.signal(signal.SIGUSR1, callback_function)

The available signal numbers on Ubuntu can be found at /usr/include/asm/signal.h.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文