【问题标题】:Terminate external program run through asyncio with specific signal使用特定信号终止通过 asyncio 运行的外部程序
【发布时间】:2017-01-08 18:19:11
【问题描述】:

我需要终止从带有特定信号(例如 SIGTERM)的 asyncio Python 脚本运行的外部程序。我的问题是,即使我向它们发送 SIGTERM 信号,程序总是会收到 SIGINT。

这是一个测试用例,下面测试中使用的fakeprg的源代码可以在here找到。

import asyncio
import traceback
import os
import os.path
import sys
import time
import signal
import shlex

from functools import partial


class ExtProgramRunner:
    run = True
    processes = []

    def __init__(self):
        pass

    def start(self, loop):
        self.current_loop = loop
        self.current_loop.add_signal_handler(signal.SIGINT, lambda: asyncio.async(self.stop('SIGINT')))
        self.current_loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.async(self.stop('SIGTERM')))
        asyncio.async(self.cancel_monitor())
        asyncio.Task(self.run_external_programs())

    @asyncio.coroutine
    def stop(self, sig):
        print("Got {} signal".format(sig))
        self.run = False
        for process in self.processes:
            print("sending SIGTERM signal to the process with pid {}".format(process.pid))
            process.send_signal(signal.SIGTERM)
        print("Canceling all tasks")
        for task in asyncio.Task.all_tasks():
            task.cancel()

    @asyncio.coroutine
    def cancel_monitor(self):
        while True:
            try:
                yield from asyncio.sleep(0.05)
            except asyncio.CancelledError:
                break
        print("Stopping loop")
        self.current_loop.stop()

    @asyncio.coroutine
    def run_external_programs(self):
        os.makedirs("/tmp/files0", exist_ok=True)
        os.makedirs("/tmp/files1", exist_ok=True)
        # schedule tasks for execution
        asyncio.Task(self.run_cmd_forever("/tmp/fakeprg /tmp/files0 1000"))
        asyncio.Task(self.run_cmd_forever("/tmp/fakeprg /tmp/files1 5000"))

    @asyncio.coroutine
    def run_cmd_forever(self, cmd):
        args = shlex.split(cmd)
        while self.run:
            process = yield from asyncio.create_subprocess_exec(*args)
            self.processes.append(process)
            exit_code = yield from process.wait()
            for idx, p in enumerate(self.processes):
                if process.pid == p.pid:
                    self.processes.pop(idx)
            print("External program '{}' exited with exit code {}, relauching".format(cmd, exit_code))


def main():
    loop = asyncio.get_event_loop()

    try:
        daemon = ExtProgramRunner()
        loop.call_soon(daemon.start, loop)

        # start main event loop
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    except asyncio.CancelledError as exc:
        print("asyncio.CancelledError")
    except Exception as exc:
        print(exc, file=sys.stderr)
        print("====", file=sys.stderr)
        print(traceback.format_exc(), file=sys.stderr)
    finally:
        print("Stopping daemon...")
        loop.close()


if __name__ == '__main__':
    main()

【问题讨论】:

    标签: python python-3.x python-asyncio


    【解决方案1】:

    这样做的原因是:当你启动你的python程序(父)并启动它的进程/tmp/fakeprg(子)时,它们会得到所有不同的进程及其pid,但它们都是run in the same foreground process group。您的 shell 绑定到该组,因此当您点击 Ctrl-C (SIGINT)、Ctrl-Y (SIGTSTP) 或 Ctrl-\ (SIGQUIT) 时,它们会被发送到所有进程 在前台进程组中。

    在您的代码中,这发生在父级甚至可以通过send_signal 将信号发送给其子级之前,因此该行向已经死亡的进程发送信号(并且应该失败,因此 IMO 这是 asyncio 的问题)。

    要解决这个问题,您可以明确地将您的子进程放入一个单独的进程组中,如下所示:

    asyncio.create_subprocess_exec(*args, preexec_fn=os.setpgrp)
    

    【讨论】:

      猜你喜欢
      • 2012-11-14
      • 2016-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-01
      • 2012-03-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多