Ada cara alternatif untuk mengatasi masalah ini jika Anda tidak ingin Ekstensi C Anda (atau ctypes DLL) diikat ke Python, seperti kasus di mana Anda ingin membuat pustaka C dengan binding dalam berbagai bahasa, Anda harus mengizinkan Ekstensi C untuk berjalan dalam waktu lama, dan Anda dapat memodifikasi Ekstensi C:
Sertakan header sinyal di Ekstensi C.
#include <signal.h>
Buat typedef penangan sinyal di Ekstensi C.
typedef void (*sighandler_t)(int);
Tambahkan penangan sinyal di ekstensi C yang akan melakukan tindakan yang diperlukan untuk menghentikan kode yang berjalan lama (setel tanda stop, dll.), dan simpan penangan sinyal Python yang ada.
sighandler_t old_sig_int_handler = signal(SIGINT, your_sig_handler);
sighandler_t old_sig_term_handler = signal(SIGTERM, your_sig_handler);
Pulihkan penangan sinyal yang ada setiap kali ekstensi C kembali. Langkah ini memastikan penangan sinyal Python diterapkan kembali.
signal(SIGINT, old_sig_int_handler);
signal(SIGTERM, old_sig_term_handler);
Jika kode yang berjalan lama terputus (bendera, dll.), kembalikan kontrol ke Python dengan kode kembali yang menunjukkan nomor sinyal.
return SIGINT;
Dengan Python, kirim sinyal yang diterima di ekstensi C.
import os
import signal
status = c_extension.run()
if status in [signal.SIGINT, signal.SIGTERM]:
os.kill(os.getpid(), status)
Python akan melakukan tindakan yang Anda harapkan, seperti memunculkan KeyboardInterrupt untuk SIGINT.
Namun, Ctrl-C tampaknya tidak berpengaruh apa pun
Ctrl-C
di shell mengirim SIGINT
ke grup proses latar depan. python
saat menerima sinyal menetapkan bendera dalam kode C. Jika ekstensi C Anda berjalan di utas utama, maka tidak ada penangan sinyal Python yang akan dijalankan (dan karena itu Anda tidak akan melihat KeyboardInterrupt
pengecualian pada Ctrl-C
) kecuali Anda memanggil PyErr_CheckSignals()
yang memeriksa flag (artinya:seharusnya tidak memperlambat Anda) dan menjalankan penangan sinyal Python jika perlu atau jika simulasi Anda mengizinkan kode Python untuk dieksekusi (mis., jika simulasi menggunakan callback Python). Berikut adalah contoh kode modul ekstensi untuk CPython yang dibuat menggunakan pybind11 yang disarankan oleh @Matt:
PYBIND11_MODULE(example, m)
{
m.def("long running_func", []()
{
for (;;) {
if (PyErr_CheckSignals() != 0)
throw py::error_already_set();
// Long running iteration
}
});
}
Jika ekstensi berjalan di utas latar maka itu cukup untuk melepaskan GIL (untuk memungkinkan kode Python berjalan di utas utama yang memungkinkan penangan sinyal untuk berjalan). PyErr_CheckSignals()
selalu mengembalikan 0
di utas latar belakang.
Terkait:Cython, Python, dan KeybordInterrupt terhapus
Python memiliki penangan sinyal yang terpasang di SIGINT
yang hanya menetapkan bendera yang diperiksa oleh loop penerjemah utama. Agar penangan ini berfungsi dengan baik, juru bahasa Python harus menjalankan kode Python.
Anda memiliki beberapa opsi yang tersedia untuk Anda:
- Gunakan
Py_BEGIN_ALLOW_THREADS
/Py_END_ALLOW_THREADS
untuk melepaskan GIL di sekitar kode ekstensi C Anda. Anda tidak dapat menggunakan fungsi Python apa pun saat tidak memegang GIL, tetapi kode Python (dan kode C lainnya) dapat berjalan bersamaan dengan utas C Anda (multithreading sebenarnya). Utas Python terpisah dapat dijalankan bersama ekstensi C dan menangkap sinyal Ctrl+C. - Siapkan
SIGINT
Anda sendiri handler dan panggil handler sinyal asli (Python).SIGINT
Anda handler kemudian dapat melakukan apa pun yang diperlukan untuk membatalkan kode ekstensi C dan mengembalikan kontrol ke juru bahasa Python.
Tidak elegan, tetapi satu-satunya pendekatan yang saya temukan juga mengganggu panggilan pustaka eksternal di C++ dan mematikan semua proses anak yang sedang berjalan.
#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