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:
- 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. - Imposta il tuo
SIGINT
gestore e chiama il gestore del segnale originale (Python). Il tuoSIGINT
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