尝试忽略SIGINT时不一致

时间:2016-07-21 20:11:44

标签: python git subprocess signals sigint

我理解在设置信号处理程序时,所有子进程都默认继承所述处理程序。

因此,以下代码按预期运行:

err

即。无论我按Ctrl-C多少,脚本都会在10秒后才会终止。

但是,如果我将通话切换为import subprocess, signal signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler subprocess.check_call( [ "sleep", "10" ] ) ,我似乎能够中断脚本。

我不明白为什么行为有所不同......有什么想法?

谢谢!

1 个答案:

答案 0 :(得分:2)

  

我理解在设置信号处理程序时,所有子进程都默认继承所述处理程序。

如上所述,虽然你的例子很好,但这并不完全正确。

signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler

从技术上讲,SIG_IGN根本不是处理程序,而是一个特殊值,告诉内核在发送信号时应该丢弃该信号。 处理程序是用户提供的函数;在幕后,当您安装处理程序时,内核会设置为将信号传递给用户代码。 1

这里的关键区别是用户代码版本要求所述用户代码继续存在,但是当一个进程运行另一个进程时,使用fork + exec或spawn或者其他任何东西(所有这些都很好地隐藏了Python subprocess.Popen接口),新进程已丢弃,甚至从未有过原始进程的用户代码。这意味着新进程中不再存在任何基于用户代码的处理程序(无论是sleep还是git还是其他任何处理程序),因此必须将信号处理恢复为默认值。

然而,当使用SIG_IGN时,处置是“立即丢弃信号”,不需要用户代码操作。因此fork + exec或spawn(再次隐藏在subprocess.Popen后面)不会强制重置信号处理。

然而,作为Barmar commented,任何过程都可以根据自己的意愿改变信号的配置。很明显,Git正在为SIGINT设置自己的新处置。

程序员应该,至少在理论上,用一些样板来编写这种代码。在Python-ese中它将翻译为:

with signal.hold([signal.SIGINT]):
    previous = signal.signal(signal.SIGINT, handler)
    if previous == signal.SIG_IGN:
        # Parent process told us NOT to catch SIGINT,
        # so we should leave it ignored.
        signal.signal(signal.SIGINT, signal.SIG_IGN)

这使用Python无法公开的一对函数(可能因为并非所有系统实际实现了它,尽管它现在是标准POSIX),包含在with上下文管理器中。如果我们首先设置信号的处置,然后检查是什么并在需要时恢复它,我们打开一个竞赛窗口,在此期间我们更改了处置。为了解决这个问题,我们可以使用POSIX sigprocmask函数暂时阻止信号(在内核中推迟一段时间),同时我们处理这个问题。一旦我们确定我们有正确的处置方式,我们就会解锁信号。如果在此期间有任何交付,它们将在解锁点处理。

这一切都没有多大帮助,因为它需要对其他程序进行修复(在安装自己的处理程序时检查信号的初始处置)。但是, 有几种方法可以解决它。最简单的方法是使用信号阻塞技术,因为阻塞掩码继承,以及任何“忽略”处置 - 并且大多数程序都不会烦恼于阻塞掩码,或者如果他们这样做,使用正确的样板(我们隐藏在不存在的Python with signal.hold(...)技巧背后的那个):

  • 在条目
  • 处检索当前掩码时调用sigprocmask来阻止信号
  • 在退出时调用sigprocmask 恢复已保存的掩码(而不仅仅是显式取消阻止)。

不幸的是,这需要调用POSIX sigprocmask函数,即使在Python 3.4中也没有公开。 Python 3.4 公开pthread_sigmask,这可能(取决于你的内核)就足够了。但是,目前尚不清楚它是否值得编码。

另一种(甚至更复杂的)处理方法是让你的Python程序像大多数shell一样进行POSIX样式的作业控制。然后,它可以决定哪个进程组应该接收tty生成的信号,例如SIGINT

1 从技术上讲,用户信号传递的内核通过称为 trampoline 的东西。这有几种不同的传统机制,它是一个相当大的头发,以确保它一切正常。