【问题标题】:Python processes stops responding to SIGTERM / SIGINT after being restartedPython 进程在重启后停止响应 SIGTERM / SIGINT
【发布时间】:2009-07-15 20:11:48
【问题描述】:

我遇到了一些使用看门狗进程运行的 python 进程的奇怪问题。

看门狗进程是用python编写的,是父进程,有一个名为start_child(name)的函数,它使用subprocess.Popen打开子进程。记录 Popen 对象,以便看门狗可以使用 poll() 监视进程,并最终在需要时使用 terminate() 结束它。 如果孩子意外死亡,看门狗会再次调用 start_child(name) 并记录新的 Popen 对象。

有7个子进程,都是python。如果我手动运行任何子进程,我可以使用 kill 发送 SIGTERM 或 SIGINT 并获得我期望的结果(进程结束)。

但是,当从看门狗进程运行时,子进程只会在 FIRST 信号之后结束。当看门狗重新启动子进程时,新的子进程不再响应 SIGTERM 或 SIGINT。我不知道是什么原因造成的。

watchdog.py

class watchdog:
    # <snip> various init stuff

    def start(self):
        self.running = true

        kids = ['app1', 'app2', 'app3', 'app4', 'app5', 'app6', 'app7']
        self.processes = {}

        for kid in kids:
            self.start_child(kid)

        self.thread = threading.Thread(target=self._monitor)
        self.thread.start()

        while self.running:
            time.sleep(10)

    def start_child(self, name):
        try:
            proc = subprocess.Popen(name)
            self.processes[name] = proc
        except:
            print "oh no"
        else:
            print "started child ok"

    def _monitor(self):
        while self.running:
            time.sleep(1)
            if self.running:
                for kid, proc in self.processes.iteritems():
                    if proc.poll() is not None: # process ended
                        self.start_child(kid)

所以发生的事情是 watchdog.start() 启动所有 7 个进程,如果我发送任何进程 SIGTERM,它就会结束,并且监视器线程会再次启动它。但是,如果我随后发送新进程 SIGTERM,它会忽略它。

我应该能够一遍又一遍地向重新启动的进程发送 kill -15 。为什么重启后就忽略了?

【问题讨论】:

  • 所以这似乎是由在 python 线程中弹出一个进程引起的。根据blogs.gentoo.org/agaffney/2005/03/18/python_sucks,Python 设置了一个信号掩码来阻止从线程启动的进程上的所有信号。什么鬼,蟒蛇?我现在正在尝试使用 ctypes 调用 sigprocmask() 并将信号掩码重置为不阻塞。

标签: python ipc freebsd


【解决方案1】:

正如这里所解释的:http://blogs.gentoo.org/agaffney/2005/03/18/python_sucks,当 Python 创建一个新线程时,它会阻塞该线程(以及线程产生的任何进程)的所有信号。

我使用通过 ctypes 调用的 sigprocmask 修复了这个问题。这可能是也可能不是“正确”的方法,但它确实有效。

在子进程中,__init__:

libc = ctypes.cdll.LoadLibrary("libc.so")
mask = '\x00' * 17 # 16 byte empty mask + null terminator 
libc.sigprocmask(3, mask, None) # '3' on FreeBSD is the value for SIG_SETMASK

【讨论】:

  • 混合 fork/exec、线程和信号中的任意两个很难做到正确。将这三者混为一谈会导致灾难。
  • 我有没有提到看门狗进程本身是一个守护进程,为了分离自己而分叉了好几次?一场美味的灾难。
  • sigprocmask() 现在计划用于 Python 3.2:bugs.python.org/issue8407>
  • 我正在编写一个运行多个服务器实例(具有自己的子进程)并测试它们的交互的测试工具。您的回答使我能够在纯 python 中做到这一点。
【解决方案2】:

在 Python 中恢复默认信号处理程序而不是通过 ctypes 不是更好吗?在您的子进程中,使用信号模块:

import signal
for sig in range(1, signal.NSIG):
    try:
        signal.signal(sig, signal.SIG_DFL)
    except RuntimeError:
        pass

在尝试设置无法捕获的信号(例如 SIGKILL)时引发 RuntimeError。

【讨论】:

  • 这不起作用,因为所有信号都被屏蔽了。不管你用 signal.signal() 做什么,进程永远不会收到信号。我确实使用 signal.signal() 为 SIGTERM 设置我的处理程序(这样我可以在退出时进行清理),但您仍然需要使用 sigprocmask 来允许进程看到 SIGTERM。
  • @gdm:对不起,我不知道在 Python 中有什么方法可以做到这一点,所以通过 ctype 调用可能是唯一的方法。
猜你喜欢
  • 2011-09-21
  • 2017-08-22
  • 2011-10-24
  • 1970-01-01
  • 2023-03-16
  • 2020-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多