Existe una forma alternativa de resolver este problema si no desea que su extensión C (o DLL ctypes) esté vinculada a Python, como en el caso de que desee crear una biblioteca C con enlaces en varios idiomas, debe permitir su Extensión C para ejecutar durante períodos prolongados, y puede modificar la Extensión C:
Incluya el encabezado de la señal en la Extensión C.
#include <signal.h>
Cree un manejador de señales 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 detención, 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 controladores de señal existentes cada vez que regrese la extensión C. Este paso garantiza que se vuelvan a aplicar los controladores de señales de Python.
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 espera, como generar una interrupción de teclado para SIGINT.
Sin embargo, Ctrl-C no parece tener ningún efecto
Ctrl-C
en el shell envía SIGINT
al grupo de procesos de primer plano. python
al recibir la señal establece una bandera en código C. Si su extensión C se ejecuta en el subproceso principal, no se ejecutará ningún controlador de señal de Python (y, por lo tanto, no verá KeyboardInterrupt
excepción en Ctrl-C
) a menos que llames a PyErr_CheckSignals()
que verifica el indicador (es decir, no debería ralentizarlo) y ejecuta los controladores 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 usa devoluciones de llamada de Python). Aquí hay un ejemplo de código de un módulo de extensión para CPython creado usando pybind11 sugerido por @Matt:
PYBIND11_MODULE(example, m)
{
m.def("long running_func", []()
{
for (;;) {
if (PyErr_CheckSignals() != 0)
throw py::error_already_set();
// Long running iteration
}
});
}
Si la extensión se ejecuta en un subproceso en segundo plano, basta con liberar GIL (para permitir que el código de Python se ejecute en el subproceso principal que permite que se ejecuten los controladores de señales). PyErr_CheckSignals()
siempre devuelve 0
en un hilo de fondo.
Relacionado:Cython, Python y KeybordInterrupt ignorados
Python tiene un controlador de señal instalado en SIGINT
que simplemente establece una bandera que es verificada por el bucle principal del intérprete. Para que este controlador funcione correctamente, el intérprete de Python debe ejecutar el código de Python.
Tienes un par de opciones disponibles para ti:
- Usar
Py_BEGIN_ALLOW_THREADS
/Py_END_ALLOW_THREADS
para liberar el GIL alrededor de su código de extensión C. No puede usar ninguna función de Python cuando no tiene el GIL, pero el código de Python (y otro código C) puede ejecutarse simultáneamente con su subproceso C (verdadero subproceso múltiple). Un subproceso de Python separado puede ejecutarse junto con la extensión C y captar señales Ctrl+C. - Configura tu propio
SIGINT
controlador y llame al controlador de señal original (Python). TuSIGINT
El controlador puede hacer lo que sea necesario para cancelar el código de extensión C y devolver el control al intérprete de Python.
No es elegante, pero es el único enfoque que encontré que también interrumpe las llamadas a bibliotecas externas en C++ y elimina cualquier proceso secundario en ejecución.
#include <csignal>
#include <pybind11/pybind11.h>
void catch_signals() {
auto handler = [](int code) { throw std::runtime_error("SIGNAL " + std::to_string(code)); };
signal(SIGINT, handler);
signal(SIGTERM, handler);
signal(SIGKILL, handler);
}
PYBIND11_MODULE(example, m)
{
m.def("some_func", []()
{
catch_signals();
// ...
});
}
import sys
from example import some_func
try:
some_func()
except RuntimeError as e:
if "SIGNAL" in str(e):
code = int(str(e).rsplit(" ", 1)[1])
sys.exit(128 + code)
raise