【问题标题】:Executing subprocesses in a Python Application after SIGINT在 SIGINT 之后在 Python 应用程序中执行子进程
【发布时间】:2018-05-23 15:52:26
【问题描述】:

以下代码取自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)

行不通。

有什么解决方法?

【问题讨论】:

  • 仅供参考,这是一个非常危险的命令。取出shell=True 并改成subprocess.Popen(['hadoop', 'fs', '-put', src, dest], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 会更安全
  • 考虑如果有人要求您上传使用touch $'$(rm -rf ~)\'$(rm -rf ~)\'' 创建的文件或包含文字反引号的文件会发生什么。
  • 如果你真的需要使用shell=True,然后去掉文字引号,改为使用pipes.quote()(在Python 2中)或shlex.quote()(在Python中3) 生成符合 POSIX 标准的名称转义。 cmd = "hadoop fs put {} {}".format(pipes.quote(src), pipes.quote(dest)) 仍然会因启动不必要的 shell 而产生性能开销,并且由于与环境变量相关的干扰而容易产生副作用,但它的风险比现在要小得多。

标签: python


【解决方案1】:

如上所述,您的问题/问题的最短答案:将 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调用时,这些标准输出写入是行缓冲的)。与此同时,父母完成并关闭了管道的末端。 test.sh 接收到SIGPIPE,处理程序将其写入文件,shell 脚本终止。


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

【讨论】:

  • 顺便说一句,function foo() { 是两种不同格式的混合体——传统的 ksh 函数语法,它只是 function foo {,没有 (),而 POSIX sh 语法,它只是 @ 987654343@ 没有function。考虑使用其中一种,至少与 bash 以外的 some shell 兼容;另见wiki.bash-hackers.org/scripting/obsolete
【解决方案2】:

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

最终的解决方案是。

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)
    [...]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-12
    • 1970-01-01
    • 1970-01-01
    • 2014-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多