如何从C扩展中引发Python异常

时间:2015-11-05 18:45:14

标签: python c exception signals python-c-extension

我想从C扩展中提升KeyboardInterrupt。

在C中,我通过以下+ setup.py创建了一个名为siginfo的模块:

static void siginfo_handler(int signum, siginfo_t *siginfo, void *context) {
    printf("got signal from '%d'", siginfo->si_pid);

    //raise(PyExc_KeyboardInterrupt) <--- i want to raise keyboard interrupt here
}

static PyObject * siginfo_register(PyObject *self, PyObject *args) {
    struct sigaction act;
    memset(&act, '\0', sizeof(act));
    act.sa_sigaction = &siginfo_handler;
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGINT, &act, NULL);

    PY_RETURN_NONE;
}

static PyMethodDef SiginfoMethods[] = {
   //blah blah filled out
};
PyMODINIT_FUNC initsiginfo(void) {
   //blah blah filled out
}

在Python中:

import siginfo
import os
siginfo.register()

print "talk to me with kill -SIGINT %d" % os.getpid()

try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    print 'got keybaord interrupt'

C扩展正在捕获SIGINT,但我不知道如何从中引发Python异常,以便我可以用Python代码捕获它。

有什么想法吗?

编辑:
我发现我可以通过以下方式引发KeyboardInterrupt:

PyErr_SetString(PyExc_KeyboardInterrupt, "SIGINT received");

但如果我在信号处理程序中提升它,它确实会出错。

如果在其他功能中引发错误,则不会出错。

为什么我不能在信号处理程序中提出它?

2 个答案:

答案 0 :(得分:4)

您在获取信号时不处于执行Python代码的状态。在提出错误之前,您需要获取GIL

static void siginfo_handler(int signum, siginfo_t *siginfo, void *context) {
    printf("got signal from '%d'\n", siginfo->si_pid);
    PyGILState_STATE gstate = PyGILState_Ensure();
    PyErr_SetString(PyExc_KeyboardInterrupt, "SIGINT received");
    PyGILState_Release(gstate);
}

答案 1 :(得分:0)

您无法从信号处理程序中调用任意Python C / API函数。信号处理程序的功能非常有限。有关详细信息,请参阅herehere

Python通过一个非常简单的信号处理程序来处理它,它将类型volatile sig_atomic_t的变量设置为1.解释器定期检查此变量,将Python异常作为常规控制流的一部分,如果它是设为1.

主要问题是解释器调用需要很长时间才能执行的C代码。在C代码返回之前,不会引发异常。

幸运的是,这种情况并不常发生,因为大多数系统调用在传递信号时都会中断。在下面的程序中(与你的程序非常相似),对time.sleep()的调用将被信号中断,控件将返回Python代码,这将引发异常。

import time
try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    print 'got keybaord interrupt'