Остановка расширения C /C++ в Django

Краткое изложение проблемы

Я реализую долго работающее расширение C/C++ в Django framework (вызываемое через запрос POST). В некоторых случаях пользователь может захотеть прервать вычисления, просто выйдя/выгрузив конкретную веб-страницу, вызывающую C-расширение. Можно ли убить расширение из Python?

Минимальный воспроизводимый пример

Для того чтобы воспроизвести проблему, мы можем сделать собственное расширение Fibonacci C. Вызов в Django выглядит следующим образом:

import fibonacci
from django.views.generic.base import TemplateView

# Computation
class ComputationView(TemplateView):
    template_name = 'Generic/computation_page.html'

    @staticmethod
    def get_results(request):
        N=1000
        if request.method == "POST":  # When button is pressed
            # Long computations
            cs_min = fibonacci.fib(N)
        else:
            cs_min = None
        return render(request, ComputationView.template_name, {'cs_min': cs_min})

В то время как код на языке C выглядит следующим образом:

#include <Python.h>

// Compute the Minimal cutsets
int Cfib(int n)
{
    int fib1;
    int fib2;
    if (n < 2)
        return n;
    else{
        Py_BEGIN_ALLOW_THREADS // Release GIL
        // Some stric operations in C
        Py_END_ALLOW_THREADS // Reacquire GIL
        return Cfib(n-1)+Cfib(n-2);
    }
}

// Our Python binding to our C function
// This will take one and only one non-keyword argument
static PyObject* fib(PyObject* self, PyObject* args)
{
    // instantiate our 'n' value
    int n; // Order of cutset

    // if our `n` value
    if(!PyArg_ParseTuple(args, "i", &n))
        return NULL;
    // return our computed fib number
    return Py_BuildValue("i", Cfib(n));
}

// Our Module's Function Definition struct
// We require this `NULL` to signal the end of our method
// definition
static PyMethodDef myMethods[] = {
    { "fib", fib, METH_VARARGS | METH_KEYWORDS, "Computes Fibonacci" },
    { NULL, NULL, 0, NULL }
};

// Our Module Definition struct
static struct PyModuleDef fibonacci = {
    PyModuleDef_HEAD_INIT,
    "fibonacci",
    "Test Module",
    -1,
    myMethods
};

// Initializes our module using our above struct
PyMODINIT_FUNC PyInit_fibonacci(void)
{
    return PyModule_Create(&fibonacci);
}

Как видите, пользователь может выполнять другие действия благодаря управлению GIL (ALLOW_THREADS), пока работает функция C.

Альтернативный подход, который я пробовал

Поскольку мне не удалось убить запущенную функцию, я попробовал напрямую убить поток, отвечающий за функцию C, сделав:

import psutil

# Get the process
current_process = psutil.Process()
# Iterate the threads and find the one which has spent more time
max_time = 0
ind_max = 0
for ind, thread in enumerate(current_process.threads()):
    if thread.user_time > max_time:
        max_time = thread.user_time
        ind_max = ind
                            
# Kill the thread with more time
# This only works in Linux !!!!!
if platform.system()!= "Windows" and max_time > 10:
    signal.pthread_kill(current_process.threads()[ind_max].id, signal.SIGINT)

Однако такой подход убивает не только ответственный поток, но и весь процесс Django.

Возможный альтернативный подход

Я знаю, что вы можете создавать исключения внутри расширения C. Например:

PyErr_SetString(PyExc_TypeError, "Custom error message");

Если бы я мог прочитать значение, хранящееся в базе данных, или передать каким-то образом аргумент (по ссылке?), который функция может постоянно читать во время работы, я мог бы достичь той же цели остановки функции. Однако, я не знаю, как это сделать.

Вернуться на верх