Остановка расширения 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");
Если бы я мог прочитать значение, хранящееся в базе данных, или передать каким-то образом аргумент (по ссылке?), который функция может постоянно читать во время работы, я мог бы достичь той же цели остановки функции. Однако, я не знаю, как это сделать.