GNU/Linux >> Znalost Linux >  >> Linux

Povolení Ctrl-C přerušit rozšíření C pythonu

Existuje alternativní způsob, jak tento problém vyřešit, pokud nechcete, aby vaše rozšíření C (nebo DLL ctypes) bylo svázáno s Pythonem, jako například v případě, kdy chcete vytvořit knihovnu C s vazbami ve více jazycích, musíte povolit C Extension běžet po dlouhou dobu a můžete upravit C Extension:

Zahrňte záhlaví signálu do rozšíření C.

#include <signal.h>

Vytvořte typ definice obslužného programu signálu v rozšíření C.

typedef void (*sighandler_t)(int);

Přidejte obslužné rutiny signálů v rozšíření C, které budou provádět akce nezbytné k přerušení jakéhokoli dlouho běžícího kódu (nastavte příznak zastavení atd.), a uloží stávající obslužné rutiny signálů Pythonu.

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

Obnovte stávající obslužné rutiny signálů, kdykoli se rozšíření C vrátí. Tento krok zajistí, že budou znovu použity obslužné rutiny signálů Pythonu.

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

Pokud je dlouho běžící kód přerušen (příznak atd.), vraťte řízení do Pythonu s návratovým kódem udávajícím číslo signálu.

return SIGINT;

V Pythonu odešlete přijatý signál v rozšíření C.

import os
import signal

status = c_extension.run()

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

Python provede akci, kterou očekáváte, jako je vyvolání přerušení klávesnice pro SIGINT.


Zdá se však, že Ctrl-C nemá žádný účinek

Ctrl-C v shellu odešle SIGINT do skupiny procesů v popředí. python při příjmu signálu nastaví příznak v kódu C. Pokud vaše rozšíření C běží v hlavním vlákně, nebude spuštěn žádný obslužný program signálu Pythonu (a proto neuvidíte KeyboardInterrupt výjimka na Ctrl-C ), pokud nezavoláte PyErr_CheckSignals() který zkontroluje příznak (to znamená:neměl by vás zpomalit) a v případě potřeby spustí obsluhu signálů Pythonu nebo pokud vaše simulace umožňuje spuštění kódu Pythonu (např. pokud simulace používá zpětná volání Pythonu). Zde je příklad kódu rozšiřujícího modulu pro CPython vytvořeného pomocí pybind11 navrženého @Matt:

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

Pokud rozšíření běží ve vlákně na pozadí, pak stačí uvolnit GIL (aby se umožnilo spuštění kódu Pythonu v hlavním vlákně, které umožňuje spuštění obslužných rutin signálů). PyErr_CheckSignals() vždy vrátí 0 ve vláknu na pozadí.

Související:Cython, Python a KeybordInterrupt ingored


Python má na SIGINT nainstalovaný obslužný program signálu což jednoduše nastaví příznak, který je kontrolován hlavní smyčkou tlumočníka. Aby tento obslužný program správně fungoval, musí na interpretu Pythonu běžet kód Pythonu.

Máte k dispozici několik možností:

  1. Použijte Py_BEGIN_ALLOW_THREADS /Py_END_ALLOW_THREADS k uvolnění GIL kolem vašeho kódu rozšíření C. Nemůžete používat žádné funkce Pythonu, když nedržíte GIL, ale kód Pythonu (a další kód C) může běžet souběžně s vaším vláknem C (skutečné multithreading). Samostatné vlákno Pythonu se může spustit spolu s rozšířením C a zachytit signály Ctrl+C.
  2. Nastavte si vlastní SIGINT handler a zavolejte původní obsluhu signálu (Python). Vaše SIGINT handler pak může udělat vše, co potřebuje, aby zrušil kód rozšíření C a vrátil řízení interpretu Pythonu.

Není to elegantní, ale našel jsem jediný přístup, který také přerušuje externí volání knihoven v C++ a zabíjí všechny běžící podřízené procesy.

#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. Převod kódu Python 2 na Python 3

  2. Proč při psaní Ctrl-c v terminálu není úloha v popředí ukončena, dokud není dokončena?

  3. Jak na to:Programování soketů v Pythonu

  1. Nastavte Python na IIS 7.5

  2. Nainstalujte tkinter pro Python

  3. Zařazení signálu do fronty v C

  1. Má Python synchronizaci?

  2. Ctrl + C přerušení zpracování událostí v Linuxu

  3. Můžeme použít C kód v Pythonu?