GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Strg-C erlauben, eine Python-C-Erweiterung zu unterbrechen

Es gibt eine alternative Möglichkeit, dieses Problem zu lösen, wenn Sie nicht möchten, dass Ihre C-Erweiterung (oder ctypes-DLL) an Python gebunden ist, z. B. wenn Sie eine C-Bibliothek mit Bindungen in mehreren Sprachen erstellen möchten, müssen Sie Ihre zulassen C-Erweiterung für längere Zeit laufen, und Sie können die C-Erweiterung ändern:

Fügen Sie den Signalheader in die C-Erweiterung ein.

#include <signal.h>

Erstellen Sie eine Signal-Handler-Typedef in der C-Erweiterung.

typedef void (*sighandler_t)(int);

Fügen Sie Signal-Handler in der C-Erweiterung hinzu, die die Aktionen ausführen, die erforderlich sind, um lang laufenden Code zu unterbrechen (Setzen eines Stopp-Flags usw.), und speichern Sie die vorhandenen Python-Signal-Handler.

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

Stellen Sie die vorhandenen Signalhandler wieder her, wenn die C-Erweiterung zurückkehrt. Dieser Schritt stellt sicher, dass die Python-Signalhandler erneut angewendet werden.

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

Wenn der lange laufende Code unterbrochen wird (Flag usw.), geben Sie die Kontrolle mit einem Rückgabecode zurück, der die Signalnummer angibt.

return SIGINT;

Senden Sie in Python das empfangene Signal in der C-Erweiterung.

import os
import signal

status = c_extension.run()

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

Python führt die erwartete Aktion aus, z. B. das Auslösen eines KeyboardInterrupt für SIGINT.


Strg-C scheint jedoch keine Wirkung zu haben

Ctrl-C in der Shell sendet SIGINT zur Vordergrundprozessgruppe. python beim Empfang des Signals wird ein Flag im C-Code gesetzt. Wenn Ihre C-Erweiterung im Hauptthread ausgeführt wird, wird kein Python-Signalhandler ausgeführt (und daher sehen Sie KeyboardInterrupt nicht Ausnahme auf Ctrl-C ), es sei denn, Sie rufen PyErr_CheckSignals() an das das Flag überprüft (es bedeutet:es sollte Sie nicht verlangsamen) und Python-Signalhandler ausführen, falls erforderlich oder wenn Ihre Simulation die Ausführung von Python-Code zulässt (z. B. wenn die Simulation Python-Callbacks verwendet). Hier ist ein Codebeispiel eines Erweiterungsmoduls für CPython, das mit pybind11 erstellt wurde, vorgeschlagen von @Matt:

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

Wenn die Erweiterung in einem Hintergrundthread ausgeführt wird, reicht es aus, GIL freizugeben (damit Python-Code im Hauptthread ausgeführt werden kann, der die Ausführung der Signalhandler ermöglicht). PyErr_CheckSignals() gibt immer 0 zurück in einem Hintergrundthread.

Verwandte:Cython, Python und KeybordInterrupt ingored


Python hat einen Signal-Handler auf SIGINT installiert die einfach ein Flag setzt, das von der Hauptinterpreterschleife überprüft wird. Damit dieser Handler ordnungsgemäß funktioniert, muss der Python-Interpreter Python-Code ausführen.

Ihnen stehen einige Optionen zur Verfügung:

  1. Verwenden Sie Py_BEGIN_ALLOW_THREADS /Py_END_ALLOW_THREADS die GIL um Ihren C-Erweiterungscode freizugeben. Sie können keine Python-Funktionen verwenden, wenn Sie die GIL nicht besitzen, aber Python-Code (und anderer C-Code) kann gleichzeitig mit Ihrem C-Thread ausgeführt werden (echtes Multithreading). Ein separater Python-Thread kann neben der C-Erweiterung ausgeführt werden und Strg+C-Signale abfangen.
  2. Erstellen Sie Ihren eigenen SIGINT Handler und rufen Sie den ursprünglichen (Python) Signalhandler auf. Ihr SIGINT Der Handler kann dann alles tun, was er tun muss, um den C-Erweiterungscode abzubrechen und die Kontrolle an den Python-Interpreter zurückzugeben.

Nicht elegant, aber der einzige Ansatz, den ich gefunden habe, der auch externe Bibliotheksaufrufe in C++ unterbricht und alle laufenden untergeordneten Prozesse beendet.

#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. Warum wird der Vordergrundjob bei der Eingabe von Strg-c in einem Terminal nicht beendet, bis er abgeschlossen ist?

  2. Gewusst wie:Socket-Programmierung in Python

  3. Installieren Sie python-novaclient unter Windows

  4. Richten Sie Python unter IIS 7.5 ein

  5. Python - Trap alle Signale

So installieren Sie Python unter Linux

So installieren Sie Python 3 unter Windows 10

Installieren Sie Python 3 auf Redhat 8

So überprüfen Sie die Python-Version

Python if..else-Anweisung

Python-Skript kann nicht mit Strg-C beendet werden