代码之家  ›  专栏  ›  技术社区  ›  Stefan Papp

在sigint之后在python应用程序中执行子进程

  •  2
  • Stefan Papp  · 技术社区  · 7 年前

    以下代码作为引用来自 Saving work after a SIGINT

    class Main(object):
        def do_stuff(self):
            ...
        def save_work(self):
            ...
        def __init__(self):
            try:
                self.do_stuff()
            except KeyboardInterrupt:
                pass # Or print helpful info
            self.save_work()
    

    在没有子流程的情况下,这是完全正常的。

    但是,一旦在save_work()中调用子进程,子进程将不会在收到sigint信号时执行。

    所以,执行

        cmd = r"hadoop fs -put '{}' '{}'".format(
            src, dest)
        process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    

    不起作用。

    解决这个问题的方法是什么?

    2 回复  |  直到 7 年前
        1
  •  0
  •   Ondrej K.    6 年前

    对您的问题/问题的最短回答如前所述:替换 subprocess.Popen 具有 subprocess.call 或者它的一个变体(例如检查)。或添加 process.communicate() .

    发生了什么,为什么看起来“不起作用”。 Popen 打开管道进行通信,并根据需要分叉处理。但是,管道在父进程端(您从中调用它的那个)没有从它读取任何内容,这实际上会导致子进程(写入stdout/stderr)非常快地进入阻塞I/O中。同时,您的父进程继续运行,因为没有任何命令它等待。它的子进程,最终在子进程接收到的点终止 SIGPIPE (默认操作将终止)。

    让我们来个 test.sh :

    #!/bin/bash
    handle_sigpipe() {
            echo "GOT SIGPIPE" >> OUT
            exit 0
    }
    trap handle_sigpipe SIGPIPE
    echo line1 > OUT
    echo line2
    echo line3 >> OUT
    

    一个小的python脚本调用它,类似于您的问题:

    import time
    import subprocess
    try:
        time.sleep(20)
    except KeyboardInterrupt:
        cmd = "./test.sh"
        process = subprocess.Popen(cmd, shell=True,
                                   stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    

    现在我们调用并中断它:

    $ python3 test.py 
    ^C
    $ cat OUT 
    line1
    GOT SIGPIPE
    

    我们写过 line1 ,但当脚本尝试写入时 line2 最后,它只是等待有人从接收端和管道中读取(至少当通过shell调用时,这些stdout写入是行缓冲的)。同时,父级完成并关闭其管道末端。 测试.sh 接收 信号管 ,处理程序将其写入文件,shell脚本将终止。


    如果您实际上希望在脚本退出时(也在中断时)执行清理/保存您的工作。 atexit 这是常见的方法。如果你想处理一个特定的信号(比如 SIGINT )你也可以看看 signal.signal .

        2
  •  0
  •   Stefan Papp    6 年前

    上述问题是由重构代码解决的。级联异常阻止了信号。

    最终的解决方案是。

    def signal_handler(sign, frame):
        logger.warn('app has been terminated manually with signal {} at frame {}'.format(sign, frame))
        sys.exit(1)
    
    def end_programm():
        upload_log_file()
    
    
    def main():
        [...]
        signal.signal(signal.SIGINT, signal_handler)
        signal.signal(signal.SIGTERM, signal_handler)
        atexit.register(end_programm)
        [...]