试图忽略 SIGINT 时不一致
Inconsistency when trying to ignore SIGINT
据我了解,当您设置信号处理程序时,所有子进程默认都会继承该处理程序。
因此,以下代码按预期运行:
import subprocess, signal
signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler
subprocess.check_call( [ "sleep", "10" ] )
即无论我按多少次 Ctrl-C,脚本都不会在 10 秒过去后终止。
但是,如果我将调用切换到 git clone xxxxxx
,似乎我 能够中断脚本。
我不明白为什么行为会有所不同...有什么想法吗?
谢谢!
I am of the understanding that when you set a signal handler, all child processes inherit said handler by default.
虽然你的例子很好,但按照措辞,这不太正确。
signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler
从技术上讲,SIG_IGN
根本不是一个处理程序,而是一个特殊的值,告诉内核信号在发送时应该被丢弃。 handler 是用户提供的函数;在幕后,当您安装处理程序时,内核会设置为将信号传递给用户代码。1
这里的主要区别在于 用户代码 版本要求所述用户代码继续存在,但是当一个进程运行另一个进程时,使用 fork+exec 或 spawn 或其他( Python subprocess.Popen
接口很好地隐藏了),新进程已经丢弃,甚至从未有过来自原始进程的用户代码。这意味着新进程中不再存在任何基于用户代码的处理程序(无论是 sleep
还是 git
或其他任何东西),因此必须将信号处理恢复为默认值。
然而,当使用 SIG_IGN
时,处置是 "discard signal immediately",不需要用户代码操作。因此 fork+exec 或 spawn(再次隐藏在 subprocess.Popen
后面)不会强制重置信号配置。
与一样,但是,任何进程都可以根据自己的意愿改变信号的配置。显然 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
上下文中经理。如果我们先简单地设置信号的配置,然后检查它 是什么 并在需要时恢复它,我们将开启一场比赛 window,在此期间我们已经 改变了性格。为了修复竞争,我们可以使用 POSIX sigprocmask
函数暂时 阻止 信号(在内核中延迟一段时间),同时我们 fiddle左右配合配置。一旦我们确定我们有正确的处置,我们就会解锁信号。如果在此期间有任何交付,它们将在解锁点处理掉。
None 这有很大帮助,因为它需要对其他程序进行修复(以在它们安装自己的处理程序时检查信号的初始配置)。但是,有 几种解决方法。最简单的是使用信号阻塞技术,因为阻塞掩码是 也 继承的,以及任何 "ignore" 处置——而且大多数程序不会为阻塞掩码而烦恼,或者如果他们这样做,请使用正确的样板(我们在此处隐藏在不存在的 Python with signal.hold(...)
技巧后面的样板):
- 调用
sigprocmask
在入口处检索当前掩码时阻止信号
- 在退出时调用
sigprocmask
以 恢复保存的掩码 (不仅仅是显式解锁)。
不幸的是,这需要调用 POSIX sigprocmask
函数,即使在 Python 3.4 中也没有公开。 Python 3.4 确实 公开了 pthread_sigmask
并且这可能(取决于您的内核)就足够了。不过,尚不清楚是否值得编写代码。
另一种(甚至更复杂)处理此问题的方法是让您的 Python 程序执行 POSIX 风格的作业控制,就像大多数 shell 一样。然后它可以决定哪个进程组应该接收 tty 生成的信号,例如 SIGINT
.
1从技术上讲,内核到用户的信号传递要经过一种叫做 trampoline 的东西。有几种不同的传统机制,要确保一切正常,这是一个相当大的问题。
据我了解,当您设置信号处理程序时,所有子进程默认都会继承该处理程序。
因此,以下代码按预期运行:
import subprocess, signal
signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler
subprocess.check_call( [ "sleep", "10" ] )
即无论我按多少次 Ctrl-C,脚本都不会在 10 秒过去后终止。
但是,如果我将调用切换到 git clone xxxxxx
,似乎我 能够中断脚本。
我不明白为什么行为会有所不同...有什么想法吗?
谢谢!
I am of the understanding that when you set a signal handler, all child processes inherit said handler by default.
虽然你的例子很好,但按照措辞,这不太正确。
signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler
从技术上讲,SIG_IGN
根本不是一个处理程序,而是一个特殊的值,告诉内核信号在发送时应该被丢弃。 handler 是用户提供的函数;在幕后,当您安装处理程序时,内核会设置为将信号传递给用户代码。1
这里的主要区别在于 用户代码 版本要求所述用户代码继续存在,但是当一个进程运行另一个进程时,使用 fork+exec 或 spawn 或其他( Python subprocess.Popen
接口很好地隐藏了),新进程已经丢弃,甚至从未有过来自原始进程的用户代码。这意味着新进程中不再存在任何基于用户代码的处理程序(无论是 sleep
还是 git
或其他任何东西),因此必须将信号处理恢复为默认值。
然而,当使用 SIG_IGN
时,处置是 "discard signal immediately",不需要用户代码操作。因此 fork+exec 或 spawn(再次隐藏在 subprocess.Popen
后面)不会强制重置信号配置。
与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
上下文中经理。如果我们先简单地设置信号的配置,然后检查它 是什么 并在需要时恢复它,我们将开启一场比赛 window,在此期间我们已经 改变了性格。为了修复竞争,我们可以使用 POSIX sigprocmask
函数暂时 阻止 信号(在内核中延迟一段时间),同时我们 fiddle左右配合配置。一旦我们确定我们有正确的处置,我们就会解锁信号。如果在此期间有任何交付,它们将在解锁点处理掉。
None 这有很大帮助,因为它需要对其他程序进行修复(以在它们安装自己的处理程序时检查信号的初始配置)。但是,有 几种解决方法。最简单的是使用信号阻塞技术,因为阻塞掩码是 也 继承的,以及任何 "ignore" 处置——而且大多数程序不会为阻塞掩码而烦恼,或者如果他们这样做,请使用正确的样板(我们在此处隐藏在不存在的 Python with signal.hold(...)
技巧后面的样板):
- 调用
sigprocmask
在入口处检索当前掩码时阻止信号 - 在退出时调用
sigprocmask
以 恢复保存的掩码 (不仅仅是显式解锁)。
不幸的是,这需要调用 POSIX sigprocmask
函数,即使在 Python 3.4 中也没有公开。 Python 3.4 确实 公开了 pthread_sigmask
并且这可能(取决于您的内核)就足够了。不过,尚不清楚是否值得编写代码。
另一种(甚至更复杂)处理此问题的方法是让您的 Python 程序执行 POSIX 风格的作业控制,就像大多数 shell 一样。然后它可以决定哪个进程组应该接收 tty 生成的信号,例如 SIGINT
.
1从技术上讲,内核到用户的信号传递要经过一种叫做 trampoline 的东西。有几种不同的传统机制,要确保一切正常,这是一个相当大的问题。