Sending ^C to Python subprocess objects on Windows

前端 未结 6 1215
离开以前
离开以前 2020-12-07 19:06

I have a test harness (written in Python) that needs to shut down the program under test (written in C) by sending it ^C. On Unix,

proc.send_sign         


        
6条回答
  •  旧巷少年郎
    2020-12-07 19:46

    There is a solution by using a wrapper (as described in the link Vinay provided) which is started in a new console window with the Windows start command.

    Code of the wrapper:

    #wrapper.py
    import subprocess, time, signal, sys, os
    
    def signal_handler(signal, frame):
      time.sleep(1)
      print 'Ctrl+C received in wrapper.py'
    
    signal.signal(signal.SIGINT, signal_handler)
    print "wrapper.py started"
    subprocess.Popen("python demo.py")
    time.sleep(3) #Replace with your IPC code here, which waits on a fire CTRL-C request
    os.kill(signal.CTRL_C_EVENT, 0)
    

    Code of the program catching CTRL-C:

    #demo.py
    
    import signal, sys, time
    
    def signal_handler(signal, frame):
      print 'Ctrl+C received in demo.py'
      time.sleep(1)
      sys.exit(0)
    
    signal.signal(signal.SIGINT, signal_handler)
    print 'demo.py started'
    #signal.pause() # does not work under Windows
    while(True):
      time.sleep(1)
    

    Launch the wrapper like e.g.:

    PythonPrompt> import subprocess
    PythonPrompt> subprocess.Popen("start python wrapper.py", shell=True)
    

    You need to add some IPC code which allows you to control the wrapper firing the os.kill(signal.CTRL_C_EVENT, 0) command. I used sockets for this purpose in my application.

    Explanation:

    Preinformation

    • send_signal(CTRL_C_EVENT) does not work because CTRL_C_EVENT is only for os.kill. [REF1]
    • os.kill(CTRL_C_EVENT) sends the signal to all processes running in the current cmd window [REF2]
    • Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP) does not work because CTRL_C_EVENT is ignored for process groups. [REF2] This is a bug in the python documentation [REF3]

    Implemented solution

    1. Let your program run in a different cmd window with the Windows shell command start.
    2. Add a CTRL-C request wrapper between your control application and the application which should get the CTRL-C signal. The wrapper will run in the same cmd window as the application which should get the CTRL-C signal.
    3. The wrapper will shutdown itself and the program which should get the CTRL-C signal by sending all processes in the cmd window the CTRL_C_EVENT.
    4. The control program should be able to request the wrapper to send the CTRL-C signal. This might be implemnted trough IPC means, e.g. sockets.

    Helpful posts were:

    I had to remove the http in front of the links because I'm a new user and are not allowed to post more than two links.

    • http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/dc9586ab-1ee8-41aa-a775-cf4828ac1239/#6589714f-12a7-447e-b214-27372f31ca11
    • Can I send a ctrl-C (SIGINT) to an application on Windows?
    • Sending SIGINT to a subprocess of python
    • http://bugs.python.org/issue9524
    • http://ss64.com/nt/start.html
    • http://objectmix.com/python/387639-sending-cntrl-c.html#post1443948

    Update: IPC based CTRL-C Wrapper

    Here you can find a selfwritten python module providing a CTRL-C wrapping including a socket based IPC. The syntax is quite similiar to the subprocess module.

    Usage:

    >>> import winctrlc
    >>> p1 = winctrlc.Popen("python demo.py")
    >>> p2 = winctrlc.Popen("python demo.py")
    >>> p3 = winctrlc.Popen("python demo.py")
    >>> p2.send_ctrl_c()
    >>> p1.send_ctrl_c()
    >>> p3.send_ctrl_c()
    

    Code

    import socket
    import subprocess
    import time
    import random
    import signal, os, sys
    
    
    class Popen:
      _port = random.randint(10000, 50000)
      _connection = ''
    
      def _start_ctrl_c_wrapper(self, cmd):
        cmd_str = "start \"\" python winctrlc.py "+"\""+cmd+"\""+" "+str(self._port)
        subprocess.Popen(cmd_str, shell=True)
    
      def _create_connection(self):
        self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._connection.connect(('localhost', self._port))
    
      def send_ctrl_c(self):
        self._connection.send(Wrapper.TERMINATION_REQ)
        self._connection.close()
    
      def __init__(self, cmd):
        self._start_ctrl_c_wrapper(cmd)
        self._create_connection()
    
    
    class Wrapper:
      TERMINATION_REQ = "Terminate with CTRL-C"
    
      def _create_connection(self, port):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(('localhost', port))
        s.listen(1)
        conn, addr = s.accept()
        return conn
    
      def _wait_on_ctrl_c_request(self, conn):
        while True:
          data = conn.recv(1024)
          if data == self.TERMINATION_REQ:
            ctrl_c_received = True
            break
          else:
            ctrl_c_received = False
        return ctrl_c_received
    
      def _cleanup_and_fire_ctrl_c(self, conn):
        conn.close()
        os.kill(signal.CTRL_C_EVENT, 0)
    
      def _signal_handler(self, signal, frame):
        time.sleep(1)
        sys.exit(0)
    
      def __init__(self, cmd, port):
        signal.signal(signal.SIGINT, self._signal_handler)
        subprocess.Popen(cmd)
        conn = self._create_connection(port)
        ctrl_c_req_received = self._wait_on_ctrl_c_request(conn)
        if ctrl_c_req_received:
          self._cleanup_and_fire_ctrl_c(conn)
        else:
          sys.exit(0)
    
    
    if __name__ == "__main__":
      command_string = sys.argv[1]
      port_no = int(sys.argv[2])
      Wrapper(command_string, port_no)
    

提交回复
热议问题