2011-08-16 5 views
21

Tengo un arnés de prueba (escrito en Python) que necesita cerrar el programa bajo prueba (escrito en C) enviándolo ^C. En Unix,Enviando objetos de subproceso^C a Python en Windows

proc.send_signal(signal.SIGINT) 

funciona perfectamente. En Windows, arroja un error ("la señal 2 no es compatible" o algo así). Estoy usando Python 2.7 para Windows, así que tengo la impresión de que yo debería ser capaz de hacer en su lugar

proc.send_signal(signal.CTRL_C_EVENT) 

pero esto no hace nada en absoluto. ¿Que tengo que hacer? Este es el código que crea el subproceso:

# Windows needs an extra argument passed to subprocess.Popen, 
# but the constant isn't defined on Unix. 
try: kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP 
except AttributeError: pass 
proc = subprocess.Popen(argv, 
         stdin=open(os.path.devnull, "r"), 
         stdout=subprocess.PIPE, 
         stderr=subprocess.PIPE, 
         **kwargs) 
+0

Esta podría ser la única manera - http://code.activestate.com/recipes/347462-terminating-a-subprocess-on-windows/ con win32api OH o ctypes. – arunkumar

+0

'subprocess.kill' llamará' TerminateProcess' para mí, pero eso no genera^C. Específicamente necesito fingir el comportamiento de escribir^C en la consola. – zwol

+0

intente esto - http://www.rutherfurd.net/python/sendkeys/. aparentemente SendKeys.SendKeys ("^ c") debería hacerlo. – arunkumar

Respuesta

7

intentar llamar a la función GenerateConsoleCtrlEvent usando ctypes. A medida que crea un nuevo grupo de procesos, la ID del grupo de procesos debe ser la misma que la del pid. Por lo tanto, algo como

import ctypes 

ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, proc.pid) # 0 => Ctrl-C 

debería funcionar.

Actualización: Tienes razón, me perdí esa parte de los detalles. Aquí está a post que sugiere una posible solución, aunque es un poco complicado. Más detalles están en this answer.

+0

Esto tampoco funcionó, así que volví a leer la página MSDN y me di cuenta de que específicamente dice" no puedes enviar CTRL_C_EVENT a un grupo de procesos, no tiene efecto " . Enviar CTRL_BREAK_EVENT en su lugar funciona (sin ctypes par), y hace exactamente lo que quiero en un programa de prueba de juguete, pero cuando lo uso en mi programa real bajo prueba, aparece el diálogo "ha encontrado un problema y debe cerrarse" caja una y otra vez. ¿Algunas ideas? – zwol

+0

@zwol: los documentos dicen * "Esta señal no se puede generar para grupos de procesos." * Pero 'os.kill (0, signal.CTRL_C_EVENT)' genera 'KeyboardInterrupt' en' ipython' en la consola de Windows para mí como si fuera ' he presionado 'Ctrl + C' manualmente, es decir, puede usar' CTRL_C_EVENT' con '0' (*" la señal se genera en todos los procesos que comparten la consola del proceso de llamada. "*). – jfs

10

Hay una solución mediante el uso de un contenedor (como se describe en el enlace Vinay proporcionado) que se inicia en una nueva ventana de la consola con el comando start de Windows.

Código de la envoltura:

#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) 

Código del programa de captura 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) 

lanzamiento del envoltorio, como por ejemplo:

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

es necesario agregar algunos Código IPC que le permite controlar el contenedor que activa el comando os.kill (signal.CTRL_C_EVENT, 0). Utilicé enchufes para este propósito en mi aplicación.

Explicación:

información previa

  • send_signal(CTRL_C_EVENT) no funciona porque CTRL_C_EVENT es sólo para os.kill. [REF1]
  • os.kill(CTRL_C_EVENT) envía la señal a todos los procesos que se ejecutan en la ventana de cmd actual [REF2]
  • Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP) no funciona porque CTRL_C_EVENT se ignora para los grupos de procesos.[REF2] trata de un error en la documentación de Python [REF3]

solución implementada

  1. Deje que su ejecución del programa en una ventana cmd diferente con el comando shell de Windows inicia .
  2. Agregue un contenedor de solicitud CTRL-C entre su aplicación de control y la aplicación que debería recibir la señal CTRL-C. El contenedor se ejecutará en la misma ventana de cmd que la aplicación que debería recibir la señal CTRL-C.
  3. El contenedor se apagará y el programa que recibirá la señal CTRL-C enviará todos los procesos en la ventana del cmd CTRL_C_EVENT.
  4. El programa de control debería poder solicitar al envoltorio que envíe la señal CTRL-C. Esto podría implementarse a través de los medios de IPC, p. enchufes

mensajes útiles fueron:

tuve que quitar el http frente a los enlaces porque soy un nuevo usuario y no se le permite colocar más de dos enlaces.

Actualización: Contenedor CTRL-C basado en IPC

Aquí puede encontrar un módulo python auto-escrito que proporciona un envoltorio CTRL-C que incluye un IPC basado en socket. La sintaxis es bastante similar al módulo de subproceso.

Uso:

>>> 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() 

Código

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) 
+0

¡Esta técnica (del padre que envía Ctrl + C a sí mismo y sus procesos relacionados) realmente funciona! Llegué de forma independiente, pero encontré que uno debe esperar en el padre hasta que se maneje SIGINT, para evitar la interrupción de la señal, p. llamadas al sistema una vez que llega. – aknuds1

+0

el orden de los argumentos es incorrecto. Debería ser 'os.kill (pid, sig)' en lugar de 'os.kill (sig, pid)'. Aunque 'os.kill (0, signal.CTRL_C_EVENT)' no interrumpe la llamada 'input()' en Python 3.5 en vm con Windows 7 (la intención es enviar Ctrl + C a todos los procesos que comparten la consola) – jfs

0

He estado tratando de esto, pero por alguna razón CTRL + INTER obras, y Ctrl + C no lo hace. Por lo tanto, el uso de os.kill(signal.CTRL_C_EVENT, 0) falla, pero al hacer os.kill(signal.CTRL_C_EVENT, 1) funciona. Me dijeron que esto tiene algo que ver con que el propietario del proceso de creación sea el único que puede pasar un ctrl c? ¿Tiene sentido?

Para aclarar, mientras ejecuta fio manualmente en una ventana de comandos parece estar ejecutándose como se esperaba. El uso de CTRL + BREAK se rompe sin almacenar el registro como se esperaba y CTRL + C termina de escribir en el archivo también como se esperaba. El problema parece estar en la señal para CTRL_C_EVENT.

Casi parece ser un error en Python, pero puede ser un error en Windows.También otra cosa, tenía una versión de cygwin en ejecución y el envío de ctrl + c en python también funcionó, pero de nuevo no estamos ejecutando windows nativos allí.

ejemplo:

import subprocess, time, signal, sys, os 
command = '"C:\\Program Files\\fio\\fio.exe" --rw=randrw --bs=1M --numjobs=8 --iodepth=64 --direct=1 ' \ 
    '--sync=0 --ioengine=windowsaio --name=test --loops=10000 ' \ 
    '--size=99901800 --rwmixwrite=100 --do_verify=0 --filename=I\\:\\test ' \ 
    '--thread --output=C:\\output.txt' 
def signal_handler(signal, frame): 
    time.sleep(1) 
    print 'Ctrl+C received in wrapper.py' 

signal.signal(signal.SIGINT, signal_handler) 
print 'command Starting' 
subprocess.Popen(command) 
print 'command started' 
time.sleep(15) 
print 'Timeout Completed' 
os.kill(signal.CTRL_C_EVENT, 0)