--- /dev/null
+import unittest
+
+from test.support import import_helper
+from test.support import threading_helper
+
+readline = import_helper.import_module("readline")
+
+
+@threading_helper.requires_working_threading()
+class TestReadlineRaces(unittest.TestCase):
+ def test_completer_delims_get_set(self):
+ def worker():
+ for _ in range(100):
+ readline.get_completer_delims()
+ readline.set_completer_delims(
+ ' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?')
+ readline.set_completer_delims(
+ ' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?')
+ readline.get_completer_delims()
+
+ threading_helper.run_concurrently(worker, nthreads=40)
+
+ # get_completer()/get_pre_input_hook() must take the module critical
+ # section like their setters do; otherwise reading and Py_NewRef-ing the
+ # stored hook races the setter replacing it (gh-153291).
+
+ def test_completer_get_set(self):
+ def setter():
+ for _ in range(1000):
+ readline.set_completer(lambda text, state: None)
+ readline.set_completer(None)
+
+ def getter():
+ for _ in range(1000):
+ readline.get_completer()
+
+ original = readline.get_completer()
+ self.addCleanup(readline.set_completer, original)
+ threading_helper.run_concurrently([setter] * 2 + [getter] * 6)
+
+ @unittest.skipUnless(hasattr(readline, "set_pre_input_hook"),
+ "needs readline.set_pre_input_hook")
+ def test_pre_input_hook_get_set(self):
+ def setter():
+ for _ in range(1000):
+ readline.set_pre_input_hook(lambda: None)
+ readline.set_pre_input_hook(None)
+
+ def getter():
+ for _ in range(1000):
+ readline.get_pre_input_hook()
+
+ original = readline.get_pre_input_hook()
+ self.addCleanup(readline.set_pre_input_hook, original)
+ threading_helper.run_concurrently([setter] * 2 + [getter] * 6)
+
+
+if __name__ == "__main__":
+ unittest.main()
import sysconfig
import tempfile
import textwrap
-import threading
import unittest
-from test import support
-from test.support import threading_helper
from test.support import verbose
from test.support.import_helper import import_module
from test.support.os_helper import unlink, temp_dir, TESTFN
self.assertIs(readline.get_pre_input_hook(), my_hook)
-@unittest.skipUnless(support.Py_GIL_DISABLED, 'these tests can only possibly fail with GIL disabled')
-class FreeThreadingTest(unittest.TestCase):
- @threading_helper.reap_threads
- @threading_helper.requires_working_threading()
- def test_free_threading(self):
- def completer_delims(b):
- b.wait()
- for _ in range(100):
- readline.get_completer_delims()
- readline.set_completer_delims(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?')
- readline.set_completer_delims(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?')
- readline.get_completer_delims()
-
- count = 40
- barrier = threading.Barrier(count)
- threads = [threading.Thread(target=completer_delims, args=(barrier,)) for _ in range(count)]
-
- with threading_helper.start_threads(threads):
- pass
-
-
if __name__ == "__main__":
unittest.main()
--- /dev/null
+Fix a data race in :func:`readline.get_completer` and
+:func:`readline.get_pre_input_hook` on the :term:`free-threaded <free
+threading>` build: the getters read the stored hook without the critical
+section that the corresponding setters hold.
static PyObject *
readline_get_pre_input_hook(PyObject *module, PyObject *Py_UNUSED(ignored))
{
- return readline_get_pre_input_hook_impl(module);
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(module);
+ return_value = readline_get_pre_input_hook_impl(module);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
}
#endif /* defined(HAVE_RL_PRE_INPUT_HOOK) */
static PyObject *
readline_get_completer(PyObject *module, PyObject *Py_UNUSED(ignored))
{
- return readline_get_completer_impl(module);
+ PyObject *return_value = NULL;
+
+ Py_BEGIN_CRITICAL_SECTION(module);
+ return_value = readline_get_completer_impl(module);
+ Py_END_CRITICAL_SECTION();
+
+ return return_value;
}
PyDoc_STRVAR(readline_get_history_item__doc__,
#ifndef READLINE_CLEAR_HISTORY_METHODDEF
#define READLINE_CLEAR_HISTORY_METHODDEF
#endif /* !defined(READLINE_CLEAR_HISTORY_METHODDEF) */
-/*[clinic end generated code: output=4bd95070973cd0e2 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=acf4e4c35191cf09 input=a9049054013a1b77]*/
/* Get pre-input hook */
/*[clinic input]
+@critical_section
readline.get_pre_input_hook
Get the current pre-input hook function.
static PyObject *
readline_get_pre_input_hook_impl(PyObject *module)
-/*[clinic end generated code: output=ad56b77a8e8981ca input=fb1e1b1fbd94e4e5]*/
+/*[clinic end generated code: output=ad56b77a8e8981ca input=fbbf0106bd015414]*/
{
readlinestate *state = get_readline_state(module);
if (state->pre_input_hook == NULL) {
}
/*[clinic input]
+@critical_section
readline.get_completer
Get the current completer function.
static PyObject *
readline_get_completer_impl(PyObject *module)
-/*[clinic end generated code: output=6e6bbd8226d14475 input=6457522e56d70d13]*/
+/*[clinic end generated code: output=6e6bbd8226d14475 input=0df9ba4107115c44]*/
{
readlinestate *state = get_readline_state(module);
if (state->completer == NULL) {