Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions Lib/test/test_free_threading/test_readline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import unittest

from test.support import threading_helper
from test.support import import_helper

readline = import_helper.import_module("readline")


@threading_helper.requires_working_threading()
class TestReadlineRaces(unittest.TestCase):
# 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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
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.
18 changes: 15 additions & 3 deletions Modules/clinic/readline.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Modules/readline.c
Original file line number Diff line number Diff line change
Expand Up @@ -578,14 +578,15 @@ readline_set_pre_input_hook_impl(PyObject *module, PyObject *function)
/* Get pre-input hook */

/*[clinic input]
@critical_section
readline.get_pre_input_hook

Get the current pre-input hook function.
[clinic start generated code]*/

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) {
Expand Down Expand Up @@ -886,14 +887,15 @@ readline_set_completer_impl(PyObject *module, PyObject *function)
}

/*[clinic input]
@critical_section
readline.get_completer

Get the current completer function.
[clinic start generated code]*/

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) {
Expand Down
Loading