2013-05-27 14 views
11

Czy istnieje sposób przerwania (Ctrl+C) skryptu w języku Python na podstawie pętli osadzonej w rozszerzeniu Cython?Cython, Python i KeyboardInterrupt ignorowane

Mam następujący skrypt Pythona:

def main(): 

    # Intantiate simulator 
    sim = PySimulator() 
    sim.Run() 

if __name__ == "__main__": 
    # Try to deal with Ctrl+C to abort the running simulation in terminal 
    # (Doesn't work...) 
    try: 
     sys.exit(main()) 
    except (KeyboardInterrupt, SystemExit): 
     print '\n! Received keyboard interrupt, quitting threads.\n' 

ten uruchamia pętlę, która jest częścią rozszerzenia C++ Cython. Następnie, naciskając Ctrl+C, KeyboardInterrupt jest generowany, ale ignorowany, a program kontynuuje działanie aż do końca symulacji.

Prace wokół znalazłem, to obsłużyć wyjątek od wewnątrz rozszerzenia poprzez łapanie sygnału SIGINT:

#include <execinfo.h> 
#include <signal.h> 

static void handler(int sig) 
{ 
    // Catch exceptions 
    switch(sig) 
    { 
    case SIGABRT: 
     fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", stderr); 
     break; 
    case SIGFPE: 
     fputs("Caught SIGFPE: arithmetic exception, such as divide by zero\n", 
      stderr); 
     break; 
    case SIGILL: 
     fputs("Caught SIGILL: illegal instruction\n", stderr); 
     break; 
    case SIGINT: 
     fputs("Caught SIGINT: interactive attention signal, probably a ctrl+c\n", 
      stderr); 
     break; 
    case SIGSEGV: 
     fputs("Caught SIGSEGV: segfault\n", stderr); 
     break; 
    case SIGTERM: 
    default: 
     fputs("Caught SIGTERM: a termination request was sent to the program\n", 
      stderr); 
     break; 
    } 
    exit(sig); 

} 

wówczas:

signal(SIGABRT, handler); 
signal(SIGFPE, handler); 
signal(SIGILL, handler); 
signal(SIGINT, handler); 
signal(SIGSEGV, handler); 
signal(SIGTERM, handler); 

Nie można dokonać tej pracy z Pythonem , a przynajmniej z Cython? Ponieważ mam zamiar przenieść moje rozszerzenie pod Windows/MinGW, byłbym wdzięczny za posiadanie czegoś mniej specyficznego dla Linuksa.

Odpowiedz

13

trzeba okresowo sprawdzać oczekujących sygnałów, na przykład, na każdy n-ty iteracji pętli symulacji:

from cpython.exc cimport PyErr_CheckSignals 

cdef Run(self): 
    while True: 
     # do some work 
     PyErr_CheckSignals() 

PyErr_CheckSignals potrwa obsługi sygnałów zainstalowany z modułem signal (w tym podnoszenie KeyboardInterrupt razie potrzeby).

PyErr_CheckSignals jest dość szybki, można go często nazwać. Zauważ, że powinno być wywoływane z głównego wątku, ponieważ Python uruchamia programy obsługi sygnałów w głównym wątku. Wywołanie go z wątków roboczych nie ma żadnego efektu.

Objaśnienie

Ponieważ sygnały dostarczane asynchronicznie w nieprzewidywalnych razy, to jest problematyczne uruchomić żadnego znaczącego kodu bezpośrednio z uchwytu sygnału. Dlatego Python kolejkuje sygnały przychodzące. Kolejka jest przetwarzana później jako część pętli interpretera.

Jeśli twój kod jest w pełni skompilowany, pętla interpretera nigdy nie jest wykonywana, a Python nie ma szansy na sprawdzenie i uruchomienie procedur obsługi sygnałów w kolejce.

+0

+1 za wyjaśnienie. Czy 'PyErr_CheckSignals()' obsługuje Ctrl-C w systemie Windows? Zdecydowałem się na niejawne sprawdzanie sygnału przez 'while while.running: time.sleep (1)' w głównym wątku, który działa tak długo jak kod Cython w innym wątku zwalnia GIL okresowo. – jfs

+0

Tak, działa w systemie Windows zgodnie z oczekiwaniami. –

+0

Użyłem '' opt out "' wrong. Mam na myśli coś przeciwnego: 's/opt out/choose to do'/above. – jfs

1

Zwolnij GIL, gdy Cython uruchamia części, które nie współpracują z Pythonem, uruchom pętlę w głównym wątku (uśpij lub sprawdź stan symulacji) i wywołaj sim.Stop() (który może ustawić flagę, którą twoja symulacja może okresowo sprawdzać) w except zamknij.

+0

OK, brzmi to sensownie (nawet gdybym potajemnie skakał, byłoby to łatwe rozwiązanie ;-). Dzięki za radę. –

3

Jeśli próbujesz obsługiwać KeyboardInterrupt w kodzie, który uwalnia GIL (na przykład, ponieważ używa cython.parallel.prange), trzeba będzie ponownie nabyć GIL zadzwonić PyErr_CheckSignals.Poniższy urywek (zaadaptowane z odpowiedzi powyżej @ Nikita-nemkin'S) ilustruje to, co trzeba zrobić:

from cpython.exc cimport PyErr_CheckSignals 
from cython.parallel import prange 

cdef Run(self) nogil: 
    with nogil: 
     for i in prange(1000000) 
      # do some work but check for signals every once in a while 
      if i % 10000 == 0: 
       with gil: 
        PyErr_CheckSignals() 
+0

Nie ma sensu wywoływać PyErr_CheckSignals, chyba że jesteś w głównym wątku. – jfs

0

Tak, używając makra sig_on and sig_off z pakietu cysignals:

from cysignals.signals cimport sig_on, sig_off 

def foo(): 
    sig_on() 
    call_c_code_that_takes_long() 
    sig_off() 

Makra sig_on i sig_off są zadeklarowane jako funkcje in cysignals/signals.pxd i zdefiniowane jako makra in cysignals/macros.h pod względem makra _sig_on_ (zdefiniowane w kategoriach funkcji _sig_on_prejmp i _sig_on_postjmp) oraz funkcji _sig_off_. Procedura obsługi sygnału dla przerwań klawiatury (SIGINT) jest instalowana here, a uzasadnienie implementacji jest przedstawione jako here.

Od cysignals == 1.6.5 obsługiwane są tylko systemy POSIX. Cython's conditional compilation może być użyty, aby śledzić to podejście wszędzie tam, gdzie jest dostępny cysignals i umożliwia kompilowanie także na systemach innych niż POSIX (bez Ctrl-C działającego na tych systemach).

W skrypcie setup.py:

compile_time_env = dict(HAVE_CYSIGNALS=False) 
# detect `cysignals` 
if cysignals is not None: 
    compile_time_env['HAVE_CYSIGNALS'] = True 
... 
c = cythonize(..., 
       compile_time_env=compile_time_env) 

iw odpowiedniej *.pyx pliku:

IF HAVE_CYSIGNALS: 
    from cysignals.signals cimport sig_on, sig_off 
ELSE: 
    # for non-POSIX systems 
    noop = lambda: None 
    sig_on = noop 
    sig_off = noop 

Zobacz także this answer.

Powiązane problemy