Permitir que Ctrl-C interrumpa una extensión C de python

Estoy ejecutando una gran simulación computacional en las extensiones de python basadas en C (hechas en casa). Ocasionalmente me equivoco y me gustaría terminar una simulación. Sin embargo, Ctrl-C no parece tener ningún efecto (aparte de imprimir ^C en la pantalla, así que tengo que detener el proceso usando kill o el monitor del sistema.

Por lo que puedo ver, python solo espera a que termine la extensión C y no se comunica con él durante este tiempo.

¿Hay alguna manera de hacer que esto funcione?

Yo rediseñaría las extensiones C para que no se ejecuten durante un largo período.

Por lo tanto, divídalos en pasos más elementales (cada uno se ejecuta durante un corto período de tiempo, por ejemplo, de 10 a 50 milisegundos), y tenga estos pasos más elementales llamados por el código de Python.

el estilo de paso de continuación puede ser relevante para entender, como un estilo de progtwigción …

Python tiene un controlador de señal instalado en SIGINT que simplemente establece un indicador que está marcado por el bucle del intérprete principal. Para que este controlador funcione correctamente, el intérprete de Python debe ejecutar el código Python.

Tienes un par de opciones disponibles para ti:

  1. Use Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS para liberar GIL alrededor de su código de extensión C. No puede usar ninguna función de Python cuando no tiene GIL, pero el código de Python (y otro código C) puede ejecutarse simultáneamente con su hilo C (multihilo verdadero). Un hilo de Python separado puede ejecutarse junto con la extensión C y capturar las señales Ctrl + C.
  2. Configure su propio controlador SIGINT y llame al controlador de señal original (Python). Su controlador SIGINT puede hacer lo que sea necesario para cancelar el código de extensión C y devolver el control al intérprete de Python.

Sin embargo, Ctrl-C no parece tener ningún efecto

Ctrl-C en el shell envía SIGINT al grupo de proceso de primer plano . python al recibir la señal pone una bandera en el código C. Si su extensión C se ejecuta en el subproceso principal, entonces no se ejecutará ningún controlador de señales de Python (y, por lo tanto, no verá la excepción KeyboardInterrupt en Ctrl-C ) a menos que llame a PyErr_CheckSignals() que verifica la bandera (significa: no debería disminuye la velocidad) y ejecuta los manejadores de señales de Python si es necesario o si su simulación permite que se ejecute el código de Python (por ejemplo, si la simulación utiliza devoluciones de llamada de Python). Si la extensión se ejecuta en un subproceso en segundo plano, entonces es suficiente liberar GIL (para permitir que el código Python se ejecute en el subproceso principal que permite que se ejecuten los manejadores de señales).

Relacionados: Cython, Python y KeybordInterrupt ingored

Existe una forma alternativa de resolver este problema si no desea que su Extensión C (o ctypes DLL) esté vinculada a Python, como en el caso de que desee crear una biblioteca C con enlaces en varios idiomas, debe permitir su La extensión C se ejecuta durante largos períodos, y puede modificar la extensión C:

Incluir el encabezado de la señal en la extensión C.

 #include  

Crear un controlador de señal typedef en la extensión C

 typedef void (*sighandler_t)(int); 

Agregue controladores de señales en la extensión C que realizarán las acciones necesarias para interrumpir cualquier código de ejecución prolongada (establecer un indicador de parada, etc.), y guardar los controladores de señales de Python existentes.

 sighandler_t old_sig_int_handler = signal(SIGINT, your_sig_handler); sighandler_t old_sig_term_handler = signal(SIGTERM, your_sig_handler); 

Restaure los manejadores de señales existentes siempre que la extensión C regrese. Este paso asegura que los manejadores de señales de Python se vuelvan a aplicar.

 signal(SIGINT, old_sig_int_handler); signal(SIGTERM, old_sig_term_handler); 

Si se interrumpe el código de ejecución prolongada (bandera, etc.), devuelva el control a Python con un código de retorno que indique el número de señal.

 return SIGINT; 

En Python, envía la señal recibida en la extensión C.

 import os import signal status = c_extension.run() if status in [signal.SIGINT, signal.SIGTERM]: os.kill(os.getpid(), status) 

Python realizará la acción que está esperando, tal como elevar un KeyboardInterrupt para SIGINT.