GNU/Linux >> Linux Esercitazione >  >> Linux

Consentire a Ctrl-C di interrompere un'estensione C di Python

C'è un modo alternativo per risolvere questo problema se non vuoi che la tua estensione C (o ctypes DLL) sia legata a Python, come nel caso in cui vuoi creare una libreria C con collegamenti in più lingue, devi consentire al tuo L'estensione C deve essere eseguita per lunghi periodi e puoi modificare l'estensione C:

Includi l'intestazione del segnale nell'estensione C.

#include <signal.h>

Crea un gestore di segnale typedef nell'estensione C.

typedef void (*sighandler_t)(int);

Aggiungi gestori di segnali nell'estensione C che eseguiranno le azioni necessarie per interrompere qualsiasi codice a esecuzione prolungata (imposta un flag di arresto, ecc.) e salva i gestori di segnali Python esistenti.

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

Ripristina i gestori di segnale esistenti ogni volta che l'estensione C ritorna. Questo passaggio assicura che i gestori di segnali Python vengano riapplicati.

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

Se il codice di lunga esecuzione viene interrotto (flag, ecc.), restituisci il controllo a Python con un codice di ritorno che indichi il numero del segnale.

return SIGINT;

In Python, invia il segnale ricevuto nell'estensione C.

import os
import signal

status = c_extension.run()

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

Python eseguirà l'azione che ti aspetti, come sollevare un KeyboardInterrupt per SIGINT.


Tuttavia, Ctrl-C non sembra avere alcun effetto

Ctrl-C nella shell invia SIGINT al gruppo di processi in primo piano. python alla ricezione del segnale imposta un flag in codice C. Se la tua estensione C viene eseguita nel thread principale, non verrà eseguito alcun gestore di segnale Python (e quindi non vedrai KeyboardInterrupt eccezione su Ctrl-C ) a meno che non chiami PyErr_CheckSignals() che controlla il flag (significa:non dovrebbe rallentarti) ed esegue i gestori di segnali Python se necessario o se la tua simulazione consente l'esecuzione del codice Python (ad esempio, se la simulazione utilizza i callback Python). Ecco un esempio di codice di un modulo di estensione per CPython creato utilizzando pybind11 suggerito da @Matt:

PYBIND11_MODULE(example, m)
{
    m.def("long running_func", []()
    {
        for (;;) {
            if (PyErr_CheckSignals() != 0)
                throw py::error_already_set();
            // Long running iteration
        }
    });
}

Se l'estensione viene eseguita in un thread in background, è sufficiente rilasciare GIL (per consentire l'esecuzione del codice Python nel thread principale che consente l'esecuzione dei gestori di segnale). PyErr_CheckSignals() restituisce sempre 0 in un thread in background.

Correlati:Cython, Python e KeybordInterrupt ingored


Python ha un gestore di segnale installato su SIGINT che imposta semplicemente un flag che viene verificato dal ciclo dell'interprete principale. Affinché questo gestore funzioni correttamente, l'interprete Python deve eseguire codice Python.

Hai a disposizione un paio di opzioni:

  1. Usa Py_BEGIN_ALLOW_THREADS /Py_END_ALLOW_THREADS per rilasciare il GIL attorno al tuo codice di estensione C. Non è possibile utilizzare alcuna funzione Python quando non si possiede il GIL, ma il codice Python (e altro codice C) può essere eseguito contemporaneamente al thread C (vero multithreading). Un thread Python separato può essere eseguito insieme all'estensione C e catturare i segnali Ctrl+C.
  2. Imposta il tuo SIGINT gestore e chiama il gestore del segnale originale (Python). Il tuo SIGINT handler può quindi fare tutto il necessario per annullare il codice dell'estensione C e restituire il controllo all'interprete Python.

Non elegante, ma è l'unico approccio che ho trovato che interrompe anche le chiamate alle librerie esterne in C++ e termina qualsiasi processo figlio in esecuzione.

#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. Conversione del codice Python 2 in Python 3

  2. Quando si digita Ctrl-c in un terminale, perché il lavoro in primo piano non viene terminato fino al completamento?

  3. Installa tkinter per Python

  4. Accodamento del segnale in C

  5. Impossibile terminare lo script Python con Ctrl-C

Come installare Python 3 su Windows 10

Installa Python 3 su Redhat 8

Come controllare la versione di Python

Python if..else Istruzione

Ctrl + C gestione degli eventi di interruzione in Linux

Possiamo usare il codice C in Python?