从 Python 中的段错误中恢复
Recover from segfault in Python
我的代码中有一些函数会随机导致 SegmentationFault
错误。我通过启用 faulthandler
来识别它们。我有点卡住了,不知道如何可靠地消除这个问题。
我正在考虑一些解决方法。由于函数随机崩溃,我可能会在失败后重试它们。问题是无法从 SegmentationFault
崩溃中恢复。
我现在最好的想法是稍微重写这些函数并 运行 通过子进程。这个解决方案将帮助我,一个崩溃的函数不会导致整个应用程序崩溃,并且可以重试。
有些功能很小而且经常执行,因此会显着降低我的应用程序的速度。有没有什么方法可以在单独的上下文中执行函数,比在出现段错误时不会使整个程序崩溃的子进程更快?
我有一些不可靠的 C 扩展每隔一段时间就会抛出段错误,并且由于我无法修复它,所以我所做的是创建一个装饰器 运行在单独的进程中包装函数。这样你就可以阻止段错误杀死主进程。
像这样:
https://gist.github.com/joezuntz/e7e7764e5b591ed519cfd488e20311f1
我的比较简单,它为我完成了工作。此外,它还允许您选择超时和默认 return 值,以防出现问题:
#! /usr/bin/env python3
# std imports
import multiprocessing as mp
def parametrized(dec):
"""This decorator can be used to create other decorators that accept arguments"""
def layer(*args, **kwargs):
def repl(f):
return dec(f, *args, **kwargs)
return repl
return layer
@parametrized
def sigsev_guard(fcn, default_value=None, timeout=None):
"""Used as a decorator with arguments.
The decorated function will be called with its input arguments in another process.
If the execution lasts longer than *timeout* seconds, it will be considered failed.
If the execution fails, *default_value* will be returned.
"""
def _fcn_wrapper(*args, **kwargs):
q = mp.Queue()
p = mp.Process(target=lambda q: q.put(fcn(*args, **kwargs)), args=(q,))
p.start()
p.join(timeout=timeout)
exit_code = p.exitcode
if exit_code == 0:
return q.get()
logging.warning('Process did not exit correctly. Exit code: {}'.format(exit_code))
return default_value
return _fcn_wrapper
所以你会像这样使用它:
@sigsev_guard(default_value=-1, timeout=60)
def your_risky_function(a,b,c,d):
...
tl;dr:您可以使用 signal
、setjmp
、longjmp
.
编写 C 代码
你有多个选择要处理SIGSEGV
:
- 使用
subprocess
库生成子进程
- 使用
multiprocessing
库进行分叉
- 编写自定义信号处理程序
子进程和 fork 已经描述过了,所以我将重点放在信号处理程序的角度。
编写信号处理程序
从内核的角度来看,SIGSEGV
与 SIGUSR1
、SIGQUIT
、SIGINT
等任何其他信号之间没有区别。
事实上,一些库(如 JVM)将它们用作通信方式。
不幸的是,您无法从 python 代码中覆盖信号处理程序。见 doc:
It makes little sense to catch synchronous errors like SIGFPE or SIGSEGV that are caused by an invalid operation in C code. Python will return from the signal handler to the C code, which is likely to raise the same signal again, causing Python to apparently hang. From Python 3.3 onwards, you can use the faulthandler module to report on synchronous errors.
这意味着错误管理应该在 C 代码中完成。
您可以编写自定义信号处理程序并使用 setjmp
和 longjmp
来保存和恢复堆栈上下文。
例如,这是一个简单的 CPython C 扩展:
#include <signal.h>
#include <setjmp.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static jmp_buf jmpctx;
void handle_segv(int signo)
{
longjmp(jmpctx, 1);
}
static PyObject *
install_sig_handler(PyObject *self, PyObject *args)
{
signal(SIGSEGV, handle_segv);
Py_RETURN_TRUE;
}
static PyObject *
trigger_segfault(PyObject *self, PyObject *args)
{
if (!setjmp(jmpctx))
{
// Assign a value to NULL pointer will trigger a seg fault
int *x = NULL;
*x = 42;
Py_RETURN_TRUE; // Will never be called
}
Py_RETURN_FALSE;
}
static PyMethodDef SpamMethods[] = {
{"install_sig_handler", install_sig_handler, METH_VARARGS, "Install SIGSEGV handler"},
{"trigger_segfault", trigger_segfault, METH_VARARGS, "Trigger a segfault"},
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef spammodule = {
PyModuleDef_HEAD_INIT,
"crash",
"Crash and recover",
-1,
SpamMethods,
};
PyMODINIT_FUNC
PyInit_crash(void)
{
return PyModule_Create(&spammodule);
}
和来电应用程序:
import crash
print("Install custom sighandler")
crash.install_sig_handler()
print("bad_func: before")
retval = crash.trigger_segfault()
print("bad_func: after (retval:", retval, ")")
这将产生以下输出:
Install custom sighandler
bad_func: before
bad_func: after (retval: False )
优缺点
优点:
- 从 OS 的角度来看,该应用只是将
SIGSEGV
捕捉为常规信号。错误处理会很快。
- 不需要分叉(如果您的应用程序包含各种文件描述符、套接字等,则并非总是可行)
- 它不需要生成子进程(并非总是可行,而且方法要慢得多)。
缺点:
- 可能会导致内存泄漏。
- 可能隐藏未定义/危险行为
请记住,分段错误是一个非常严重的错误!
始终尝试首先修复它而不是隐藏它。
很少有链接和参考:
- https://docs.python.org/3/library/signal.html#execution-of-python-signal-handlers
- How to write a signal handler to catch SIGSEGV?
- https://docs.python.org/3/extending/extending.html#extending-python-with-c-or-c
- https://www.cplusplus.com/reference/csetjmp/setjmp/
- https://www.cplusplus.com/reference/csetjmp/longjmp/
我的代码中有一些函数会随机导致 SegmentationFault
错误。我通过启用 faulthandler
来识别它们。我有点卡住了,不知道如何可靠地消除这个问题。
我正在考虑一些解决方法。由于函数随机崩溃,我可能会在失败后重试它们。问题是无法从 SegmentationFault
崩溃中恢复。
我现在最好的想法是稍微重写这些函数并 运行 通过子进程。这个解决方案将帮助我,一个崩溃的函数不会导致整个应用程序崩溃,并且可以重试。
有些功能很小而且经常执行,因此会显着降低我的应用程序的速度。有没有什么方法可以在单独的上下文中执行函数,比在出现段错误时不会使整个程序崩溃的子进程更快?
我有一些不可靠的 C 扩展每隔一段时间就会抛出段错误,并且由于我无法修复它,所以我所做的是创建一个装饰器 运行在单独的进程中包装函数。这样你就可以阻止段错误杀死主进程。
像这样: https://gist.github.com/joezuntz/e7e7764e5b591ed519cfd488e20311f1
我的比较简单,它为我完成了工作。此外,它还允许您选择超时和默认 return 值,以防出现问题:
#! /usr/bin/env python3
# std imports
import multiprocessing as mp
def parametrized(dec):
"""This decorator can be used to create other decorators that accept arguments"""
def layer(*args, **kwargs):
def repl(f):
return dec(f, *args, **kwargs)
return repl
return layer
@parametrized
def sigsev_guard(fcn, default_value=None, timeout=None):
"""Used as a decorator with arguments.
The decorated function will be called with its input arguments in another process.
If the execution lasts longer than *timeout* seconds, it will be considered failed.
If the execution fails, *default_value* will be returned.
"""
def _fcn_wrapper(*args, **kwargs):
q = mp.Queue()
p = mp.Process(target=lambda q: q.put(fcn(*args, **kwargs)), args=(q,))
p.start()
p.join(timeout=timeout)
exit_code = p.exitcode
if exit_code == 0:
return q.get()
logging.warning('Process did not exit correctly. Exit code: {}'.format(exit_code))
return default_value
return _fcn_wrapper
所以你会像这样使用它:
@sigsev_guard(default_value=-1, timeout=60)
def your_risky_function(a,b,c,d):
...
tl;dr:您可以使用 signal
、setjmp
、longjmp
.
你有多个选择要处理SIGSEGV
:
- 使用
subprocess
库生成子进程 - 使用
multiprocessing
库进行分叉 - 编写自定义信号处理程序
子进程和 fork 已经描述过了,所以我将重点放在信号处理程序的角度。
编写信号处理程序
从内核的角度来看,SIGSEGV
与 SIGUSR1
、SIGQUIT
、SIGINT
等任何其他信号之间没有区别。
事实上,一些库(如 JVM)将它们用作通信方式。
不幸的是,您无法从 python 代码中覆盖信号处理程序。见 doc:
It makes little sense to catch synchronous errors like SIGFPE or SIGSEGV that are caused by an invalid operation in C code. Python will return from the signal handler to the C code, which is likely to raise the same signal again, causing Python to apparently hang. From Python 3.3 onwards, you can use the faulthandler module to report on synchronous errors.
这意味着错误管理应该在 C 代码中完成。
您可以编写自定义信号处理程序并使用 setjmp
和 longjmp
来保存和恢复堆栈上下文。
例如,这是一个简单的 CPython C 扩展:
#include <signal.h>
#include <setjmp.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static jmp_buf jmpctx;
void handle_segv(int signo)
{
longjmp(jmpctx, 1);
}
static PyObject *
install_sig_handler(PyObject *self, PyObject *args)
{
signal(SIGSEGV, handle_segv);
Py_RETURN_TRUE;
}
static PyObject *
trigger_segfault(PyObject *self, PyObject *args)
{
if (!setjmp(jmpctx))
{
// Assign a value to NULL pointer will trigger a seg fault
int *x = NULL;
*x = 42;
Py_RETURN_TRUE; // Will never be called
}
Py_RETURN_FALSE;
}
static PyMethodDef SpamMethods[] = {
{"install_sig_handler", install_sig_handler, METH_VARARGS, "Install SIGSEGV handler"},
{"trigger_segfault", trigger_segfault, METH_VARARGS, "Trigger a segfault"},
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef spammodule = {
PyModuleDef_HEAD_INIT,
"crash",
"Crash and recover",
-1,
SpamMethods,
};
PyMODINIT_FUNC
PyInit_crash(void)
{
return PyModule_Create(&spammodule);
}
和来电应用程序:
import crash
print("Install custom sighandler")
crash.install_sig_handler()
print("bad_func: before")
retval = crash.trigger_segfault()
print("bad_func: after (retval:", retval, ")")
这将产生以下输出:
Install custom sighandler
bad_func: before
bad_func: after (retval: False )
优缺点
优点:
- 从 OS 的角度来看,该应用只是将
SIGSEGV
捕捉为常规信号。错误处理会很快。 - 不需要分叉(如果您的应用程序包含各种文件描述符、套接字等,则并非总是可行)
- 它不需要生成子进程(并非总是可行,而且方法要慢得多)。
缺点:
- 可能会导致内存泄漏。
- 可能隐藏未定义/危险行为
请记住,分段错误是一个非常严重的错误! 始终尝试首先修复它而不是隐藏它。
很少有链接和参考:
- https://docs.python.org/3/library/signal.html#execution-of-python-signal-handlers
- How to write a signal handler to catch SIGSEGV?
- https://docs.python.org/3/extending/extending.html#extending-python-with-c-or-c
- https://www.cplusplus.com/reference/csetjmp/setjmp/
- https://www.cplusplus.com/reference/csetjmp/longjmp/