GNU/Linux >> Tutoriales Linux >  >> Linux

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

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:

  1. 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.
  2. Configura tu propio SIGINT controlador y llame al controlador de señal original (Python). Tu SIGINT 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

Linux
  1. Convertir su código de Python 2 a Python 3

  2. Al escribir Ctrl-c en una terminal, ¿por qué el trabajo en primer plano no finaliza hasta que se completa?

  3. Instalar python-novaclient en Windows

  4. Configurar Python en IIS 7.5

  5. Python - Atrapa todas las señales

Cómo instalar Python 3 en Windows 10

Instalar python 3 en Redhat 8

Cómo comprobar la versión de Python

Declaración de Python if..else

¿Cuál es la forma correcta de hacer que mi aplicación PyQt se cierre cuando se elimina desde la consola (Ctrl-C)?

No se puede matar el script de Python con Ctrl-C