From b3a38438d83245e52dff80f348c7fde539333cea Mon Sep 17 00:00:00 2001 From: Alper Date: Tue, 21 Oct 2025 17:14:48 -0700 Subject: [PATCH] gh-116738: Make _suggestions module thread-safe (gh-140321) --- .../test_free_threading/test_suggestions.py | 24 +++++++++++++++++++ ...-10-18-19-52-20.gh-issue-116738.NLJW0L.rst | 2 ++ Modules/_suggestions.c | 5 ++-- Modules/clinic/_suggestions.c.h | 5 +++- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100755 Lib/test/test_free_threading/test_suggestions.py create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-18-19-52-20.gh-issue-116738.NLJW0L.rst diff --git a/Lib/test/test_free_threading/test_suggestions.py b/Lib/test/test_free_threading/test_suggestions.py new file mode 100755 index 000000000000..2c10a511b865 --- /dev/null +++ b/Lib/test/test_free_threading/test_suggestions.py @@ -0,0 +1,24 @@ +import unittest + +from test.support import import_helper, threading_helper +from test.support.threading_helper import run_concurrently + +suggestions = import_helper.import_module("_suggestions") + +NTHREADS = 10 + + +@threading_helper.requires_working_threading() +class SuggestionsTests(unittest.TestCase): + def test_generate_suggestions(self): + candidates = [str(i) for i in range(100)] + + def worker(): + _ = suggestions._generate_suggestions(candidates, "42") + candidates.clear() + + run_concurrently(worker_func=worker, nthreads=NTHREADS) + + +if __name__ == "__main__": + unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-18-19-52-20.gh-issue-116738.NLJW0L.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-18-19-52-20.gh-issue-116738.NLJW0L.rst new file mode 100644 index 000000000000..bf323b870bc6 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-18-19-52-20.gh-issue-116738.NLJW0L.rst @@ -0,0 +1,2 @@ +Make _suggestions module thread-safe on the :term:`free threaded ` build. diff --git a/Modules/_suggestions.c b/Modules/_suggestions.c index b8bc6db24772..fb588de78085 100644 --- a/Modules/_suggestions.c +++ b/Modules/_suggestions.c @@ -8,6 +8,7 @@ module _suggestions /*[clinic end generated code: output=da39a3ee5e6b4b0d input=e58d81fafad5637b]*/ /*[clinic input] +@critical_section candidates _suggestions._generate_suggestions candidates: object item: unicode @@ -18,7 +19,7 @@ Returns the candidate in candidates that's closest to item static PyObject * _suggestions__generate_suggestions_impl(PyObject *module, PyObject *candidates, PyObject *item) -/*[clinic end generated code: output=79be7b653ae5e7ca input=ba2a8dddc654e33a]*/ +/*[clinic end generated code: output=79be7b653ae5e7ca input=92861a6c9bd8f667]*/ { // Check if dir is a list if (!PyList_CheckExact(candidates)) { @@ -29,7 +30,7 @@ _suggestions__generate_suggestions_impl(PyObject *module, // Check if all elements in the list are Unicode Py_ssize_t size = PyList_Size(candidates); for (Py_ssize_t i = 0; i < size; ++i) { - PyObject *elem = PyList_GetItem(candidates, i); + PyObject *elem = PyList_GET_ITEM(candidates, i); if (!PyUnicode_Check(elem)) { PyErr_SetString(PyExc_TypeError, "all elements in 'candidates' must be strings"); return NULL; diff --git a/Modules/clinic/_suggestions.c.h b/Modules/clinic/_suggestions.c.h index 51484b13d5af..3b3ed5056ac8 100644 --- a/Modules/clinic/_suggestions.c.h +++ b/Modules/clinic/_suggestions.c.h @@ -2,6 +2,7 @@ preserve [clinic start generated code]*/ +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(_suggestions__generate_suggestions__doc__, @@ -33,9 +34,11 @@ _suggestions__generate_suggestions(PyObject *module, PyObject *const *args, Py_s goto exit; } item = args[1]; + Py_BEGIN_CRITICAL_SECTION(candidates); return_value = _suggestions__generate_suggestions_impl(module, candidates, item); + Py_END_CRITICAL_SECTION(); exit: return return_value; } -/*[clinic end generated code: output=1d8e963cdae30b13 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1690dd15a464d19c input=a9049054013a1b77]*/ -- 2.47.3