diff --git a/mediawiki-php-wikidiff2 b/mediawiki-php-wikidiff2 index e487d37..d8f34d2 160000 --- a/mediawiki-php-wikidiff2 +++ b/mediawiki-php-wikidiff2 @@ -1 +1 @@ -Subproject commit e487d37c58bd2147eef3bdd2e65544f1465f839a +Subproject commit d8f34d2e7281bcbfa66d28939c3d03f25352ef0a diff --git a/pywikidiff2/pywikidiff2.cpp b/pywikidiff2/pywikidiff2.cpp index bf705b1..3b87f14 100644 --- a/pywikidiff2/pywikidiff2.cpp +++ b/pywikidiff2/pywikidiff2.cpp @@ -1,6 +1,9 @@ #define PY_SSIZE_T_CLEAN #include #include +#include +#include +#include #include "structmember.h" #undef HAVE_CONFIG_H // this disables the php allocator #include "../mediawiki-php-wikidiff2/src/lib/Wikidiff2.h" @@ -18,6 +21,7 @@ using wikidiff2::InlineJSONFormatter; typedef struct { PyObject_HEAD Wikidiff2::Config config; + bool timed_out; } pywikidiff2Obj; // here's where we set default configuration values @@ -37,6 +41,8 @@ pywikidiff2_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->config.wordsCacheCapacity = 10000; self->config.diffCacheCapacity = 10000; self->config.statsCacheCapacity = 100000; + self->config.cancelFlag = nullptr; + self->timed_out = false; return (PyObject *) self; } @@ -97,23 +103,49 @@ static PyObject *pywikidiff2_finalSplitThreshold(pywikidiff2Obj *self, PyObject return PyFloat_FromDouble(self->config.finalSplitThreshold); } +static PyObject *pywikidiff2_timed_out(pywikidiff2Obj *self, PyObject *Py_UNUSED(ignored)){ + return PyBool_FromLong(self->timed_out); +} + static Wikidiff2::String char_to_string(char* cstr){ Wikidiff2::String str(cstr, strlen(cstr)); return str; } -static Wikidiff2::String wikidiff2_inline_json_diff(pywikidiff2Obj *self, char* text1, char* text2){ +static Wikidiff2::String wikidiff2_inline_json_diff(pywikidiff2Obj *self, char* text1, char* text2, long timeout_ms){ + std::atomic cancelFlag(false); + self->config.cancelFlag = &cancelFlag; + self->timed_out = false; + + std::thread timerThread; + if (timeout_ms > 0) { + timerThread = std::thread([&cancelFlag, timeout_ms]() { + std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms)); + cancelFlag.store(true, std::memory_order_relaxed); + }); + } + Wikidiff2::String ret; Py_BEGIN_ALLOW_THREADS Wikidiff2::String str1(text1, strlen(text1)); Wikidiff2::String str2(text2, strlen(text2)); - Wikidiff2 wikidiff2( *(&self->config)); + Wikidiff2 wikidiff2(self->config); InlineJSONFormatter formatter; wikidiff2.addFormatter(formatter); wikidiff2.execute(str1, str2); ret = formatter.getResult().str(); Py_END_ALLOW_THREADS + + // Check if we timed out before stopping the timer thread + self->timed_out = cancelFlag.load(std::memory_order_relaxed); + + if (timerThread.joinable()) { + cancelFlag.store(true, std::memory_order_relaxed); // Stop timer if still waiting + timerThread.join(); + } + + self->config.cancelFlag = nullptr; return ret; } @@ -186,7 +218,7 @@ static PyObject *pywikidiff2_inline_json_diff_sequence(pywikidiff2Obj *self, PyO text = new char[text_len]; strcpy(text, input_texts_str[i]); } - Wikidiff2::String diff_str = wikidiff2_inline_json_diff(self, last_text, text); + Wikidiff2::String diff_str = wikidiff2_inline_json_diff(self, last_text, text, 0); PyObject* py_diff = PyUnicode_FromFormat("%s",diff_str.c_str()); PyList_SetItem(result_list, i-1, py_diff); } @@ -196,30 +228,33 @@ static PyObject *pywikidiff2_inline_json_diff_sequence(pywikidiff2Obj *self, PyO } -static PyObject *pywikidiff2_inline_json_diff(pywikidiff2Obj *self, PyObject *args, PyObject *kwargs){ - static char* kwdlist[] = {"text1", "text2", "numContextLines", NULL}; +static PyObject *pywikidiff2_inline_json_diff(pywikidiff2Obj *self, PyObject *args, PyObject *kwargs){ + static char* kwdlist[] = {"text1", "text2", "numContextLines", "timeout_ms", NULL}; char* text1; char* text2; - long numContextLines = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|$i", + int numContextLines = 0; + long timeout_ms = 0; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|il", kwdlist, &text1, &text2, - &numContextLines + &numContextLines, + &timeout_ms )){ PyErr_SetString(PyExc_ValueError, "Error in arguments to inline_json_diff"); return NULL; } - if(numContextLines != NULL){ + if(numContextLines != 0){ self->config.numContextLines = numContextLines; - } - Wikidiff2::String ret = wikidiff2_inline_json_diff(self, text1, text2); + } + Wikidiff2::String ret = wikidiff2_inline_json_diff(self, text1, text2, timeout_ms); return PyUnicode_FromFormat("%s",ret.c_str()); }; static PyMethodDef pywikidiff2_methods[] = { {"inline_json_diff", (PyCFunction) pywikidiff2_inline_json_diff, METH_VARARGS | METH_KEYWORDS, "run wikidiff 2 on text1 and text2"}, {"inline_json_diff_sequence", (PyCFunction) pywikidiff2_inline_json_diff_sequence, METH_VARARGS | METH_KEYWORDS, "run wikidiff 2 on a series of texts"}, + {"timed_out", (PyCFunction) pywikidiff2_timed_out, METH_NOARGS, "Whether the last diff timed out"}, {"num_context_lines", (PyCFunction) pywikidiff2_numContextLines, METH_NOARGS, "number of equal lines to output in the context of a diff"}, {"moved_line_threshold", (PyCFunction) pywikidiff2_movedLineThreshold, METH_NOARGS, "The minimum similarity a pair of lines must have to be detected as a moved line. If present, this overrides php.ini wikidiff2.moved_line_threshold"}, {"change_threshold", (PyCFunction) pywikidiff2_changeThreshold, METH_NOARGS, "Changed lines with a similarity value below this threshold will be split into a deleted line and added line. This helps matching up moved lines in some cases."},