14

Is there a way to interrupt (Ctrl+C) a Python script based on a loop that is embedded in a Cython extension?

I have the following python script:

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'

This runs a loop that is part of a C++ Cython extension. Then, while pressing Ctrl+C, the KeyboardInterrupt is thrown but ignored, and the program keeps going until the end of the simulation.

The work around I found, is to handle the exception from within the extension by catching the SIGINT signal :

#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);

}

Then :

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

Can't I make this work from Python, or at least from Cython instead ? As I am about to port my extension under Windows/MinGW, I would appreciate to have something less Linux specific.

4

4 回答 4

15

您必须定期检查未决信号,例如,在模拟循环的每 N 次迭代中:

from cpython.exc cimport PyErr_CheckSignals

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

PyErr_CheckSignals将运行与信号KeyboardInterrupt模块一起安装的信号处理程序(这包括必要时引发)。

PyErr_CheckSignals速度挺快的,经常调用就OK了。请注意,它应该从主线程调用,因为 Python 在主线程中运行信号处理程序。从工作线程调用它没有效果。

解释

由于信号在不可预测的时间异步传递,因此直接从信号处理程序运行任何有意义的代码是有问题的。因此,Python 对传入信号进行排队。队列稍后作为解释器循环的一部分进行处理。

如果您的代码已完全编译,则永远不会执行解释器循环,并且 Python 没有机会检查和运行排队的信号处理程序。

于 2013-05-27T17:36:13.127 回答
3

如果您尝试处理KeyboardInterrupt释放 GIL 的代码(例如,因为它使用cython.parallel.prange),则需要重新获取 GIL 才能调用PyErr_CheckSignals. 以下片段(改编自上面的@nikita-nemkin 的回答)说明了您需要做什么:

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()
于 2016-03-29T16:39:01.647 回答
1

当 Cython 运行不与 Python 接口的部分时释放 GIL,在主线程中运行循环(睡眠或检查模拟状态),并sim.Stop()在关闭时调用(可以设置一些您的模拟可以定期检查的标志)except

于 2013-05-27T11:27:56.927 回答
0

是的,使用宏sig_onsig_offcysignals

from cysignals.signals cimport sig_on, sig_off

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

sig_onsig_off被声明为 中的函数cysignals/signals.pxd根据宏(根据函数和定义)和函数定义为宏。键盘中断 () 的信号处理程序安装在此处,并在此处概述了实现原理。cysignals/macros.h_sig_on__sig_on_prejmp_sig_on_postjmp_sig_off_SIGINT

从 开始cysignals == 1.6.5,仅支持 POSIX 系统。可以使用Cython 的条件编译以便在任何cysignals可用的地方遵循这种方法,并允许在非 POSIX 系统上进行编译(在这些系统上不使用 Ctrl-C)。

在脚本中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)

并在相关*.pyx文件中:

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

另请参阅此答案

于 2018-01-12T03:11:26.673 回答