Python中的segfault恢复

2024-04-20 14:42:54 发布

您现在位置:Python中文网/ 问答频道 /正文

我的代码中有一些函数会随机导致SegmentationFault错误。我通过启用faulthandler来识别它们。我有点卡住了,不知道如何可靠地消除这个问题

我在考虑一些变通办法。由于函数是随机崩溃的,我可能会在失败后重试它们。问题是没有办法从SegmentationFault崩溃中恢复。
目前我最好的想法是重写这些函数,并通过子进程运行它们。这个解决方案将帮助我,使崩溃的函数不会使整个应用程序崩溃,并且可以重试

有些功能非常小,而且经常执行,因此它会显著降低我的应用程序的速度。是否有任何方法可以在单独的上下文中执行函数,速度比子进程快,在发生SEGFULT时不会使整个程序崩溃


Tags: 方法函数代码功能应用程序进程错误解决方案
2条回答

tl;dr:您可以使用signalsetjmplongjmp编写C代码


您有多个选择来处理SIGSEGV

  • 使用subprocess库发送子进程
  • 使用multiprocessing库进行分叉
  • 编写自定义信号处理程序

子流程和fork已经被描述过了,所以我将重点讨论信号处理程序的观点

写入信号处理器

从内核的角度来看,SIGSEGV和任何其他信号(如SIGUSR1SIGQUITSIGINT)之间没有区别。 事实上,一些库(如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代码中完成

您可以编写自定义信号处理程序,并使用setjmplongjmp保存和恢复堆栈上下文

例如,下面是一个简单的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 )

利弊

优点:

  • 从操作系统的角度来看,应用程序只是将SIGSEGV捕捉为常规信号。错误处理会很快
  • 它不需要分叉(如果您的应用程序包含各种类型的文件描述符、套接字等,则不总是可能)
  • 它不需要生成子进程(不总是可能的,而且速度慢得多)

缺点:

  • 可能会导致内存泄漏
  • 可能隐藏未定义/危险的行为

请记住,分段错误是一个非常严重的错误! 始终尝试先修复它,而不是隐藏它

很少有链接和参考:

我偶尔会遇到一些不可靠的C扩展抛出segfaults,由于我无法修复这些错误,所以我所做的就是创建一个decorator,在单独的进程中运行包装函数。这样就可以阻止SEGFULTS终止主进程

大概是这样的: https://gist.github.com/joezuntz/e7e7764e5b591ed519cfd488e20311f1

我的有点简单,它帮了我的忙。此外,它还允许您在出现问题时选择超时和默认返回值:

#! /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):
    ...

相关问题 更多 >