【问题标题】:Python: How to prevent subprocesses from receiving CTRL-C / Control-C / SIGINTPython:如何防止子进程接收 CTRL-C / Control-C / SIGINT
【发布时间】:2024-01-17 06:10:01
【问题描述】:

我目前正在为在 shell 中运行的专用服务器开发一个包装器。包装器通过子进程生成服务器进程,并观察其输出并对其做出反应。

必须明确地给专用服务器一个正常关闭的命令。因此,CTRL-C 不能到达服务器进程。

如果我在 python 中捕获 KeyboardInterrupt 异常或覆盖 SIGINT 处理程序,服务器进程仍会收到 CTRL-C 并立即停止。

所以我的问题是: 如何防止子进程接收到 CTRL-C / Control-C / SIGINT?

【问题讨论】:

  • 我对解决方法很感兴趣,如果您发布它会很好!
  • 你使用的是什么操作系统?
  • 专用服务器在 Linux 系统 (Debian) 上运行。
  • 有人知道 Windows 上的解决方案吗?

标签: python subprocess signals sigint keyboardinterrupt


【解决方案1】:

经过一个小时的各种尝试,这对我有用:

process = subprocess.Popen(["someprocess"], creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP)

这是windows的解决方案。

【讨论】:

    【解决方案2】:

    你可以这样做让它在 windows 和 unix 中工作:

    import subprocess
    import sys
    
    def pre_exec():
        # To ignore CTRL+C signal in the new process
        signal.signal(signal.SIGINT, signal.SIG_IGN)
    
    if sys.platform.startswith('win'):
        #https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
        #CREATE_NEW_PROCESS_GROUP=0x00000200 -> If this flag is specified, CTRL+C signals will be disabled
        my_sub_process=subprocess.Popen(["executable"], creationflags=0x00000200)
    else:
        my_sub_process=subprocess.Popen(["executable"], preexec_fn = pre_exec)
    

    【讨论】:

    • 当我使用你的creationflags 时,在 Windows 上使用 Ctrl+C 无法杀死主进程。想法?
    • @Fuzzyma 我找到了一个快速解决方法,方法是使用win32api 而不是信号:win32api.SetConsoleCtrlHandler(exit_handler, True)。像魅力一样工作。
    【解决方案3】:

    #python IRC-Channel (Freenode) 中的某个人通过指出 subprocess.Popen(...)preexec_fn 参数帮助我:

    如果 preexec_fn 设置为可调用 对象,这个对象将被调用 子进程就在 孩子被处决。 (仅限 Unix)

    因此,以下代码解决了这个问题(仅限 UNIX):

    import subprocess
    import signal
    
    def preexec_function():
        # Ignore the SIGINT signal by setting the handler to the standard
        # signal handler SIG_IGN.
        signal.signal(signal.SIGINT, signal.SIG_IGN)
    
    my_process = subprocess.Popen(
        ["my_executable"],
        preexec_fn = preexec_function
    )
    

    注意:信号实际上并没有被阻止到达子进程。相反,上面的 preexec_fn 会覆盖信号的默认处理程序,从而忽略信号。因此,如果子进程再次覆盖 SIGINT 处理程序,此解决方案可能不起作用。

    另一个注意事项:此解决方案适用于各种子流程,即它也不限于用 Python 编写的子流程。例如,我正在为其编写包装器的专用服务器实际上是用 Java 编写的。

    【讨论】:

      【解决方案4】:

      结合其他一些可以解决问题的答案 - 发送到主应用程序的任何信号都不会转发到子进程。

      import os
      from subprocess import Popen
      
      def preexec(): # Don't forward signals.
          os.setpgrp()
      
      Popen('whatever', preexec_fn = preexec)
      

      【讨论】:

      • +1 你不需要preexec 功能,Popen(args, preexec_nf=os.setpgrp) 也很酷。
      • preexec_nf?最好试试Popen(args, preexec_fn=os.setpgrp) ;-)
      【解决方案5】:

      在生成子进程之前尝试将 SIGINT 设置为忽略(之后将其重置为默认行为)。

      如果这不起作用,您需要阅读 job control 并学习如何将进程放入自己的后台进程组中,这样 ^C 甚至都不会使内核首先将信号发送给它。 (如果不编写 C 助手,在 Python 中可能无法实现。)

      另见this older question

      【讨论】:

      • 我试过这个,但它不起作用(忽略 SIGINT 之前产生子进程,即不是作业控制的东西)。我现在有一个解决方法,我将在今天晚些时候或明天介绍。