gh-153291: Fix data race in readline.get_completer() and get_pre_input_hook()#153362
gh-153291: Fix data race in readline.get_completer() and get_pre_input_hook()#153362harjothkhara wants to merge 2 commits into
Conversation
| readline.set_pre_input_hook(my_hook) | ||
| self.assertIs(readline.get_pre_input_hook(), my_hook) | ||
|
|
||
| def test_get_completer(self): |
There was a problem hiding this comment.
Is this test necessary? It didn't fail on main for me.
There was a problem hiding this comment.
Good point, that test just checks basic get/set behavior and passes on main, so I've dropped it. The free-threaded tests below are the ones that actually catch the bug.
…e_input_hook() The setters store these hooks while holding the module critical section (via set_hook's Py_XSETREF), but the getters read and Py_NewRef the same fields without it. Annotate both getters with @critical_section, matching the other readline functions (pythongh-126895).
80af1eb to
704749c
Compare
| @threading_helper.requires_working_threading() | ||
| def test_free_threading_get_completer(self): | ||
| def completer(b): | ||
| b.wait() |
There was a problem hiding this comment.
Instead of passing the barrier as an argument, you can just reference it directly:
| b.wait() | |
| barrier.wait() |
You could then remove the arguments from the args parameter.
Same with the other test.
|
I think the tests need to get moved to |
…hreading The Argument Clinic getters are exercised there under the TSan CI job (test_free_threading is in TSAN_TESTS; test_readline is not).
|
Moved them to |
|
This other test probably also needs to run with TSan: cpython/Lib/test/test_readline.py Lines 449 to 463 in b2d8db1 It might be a good idea to move it in this PR although that's probably better answered by someone else |
readline.get_completer()readsstate->completerand returns a new reference without taking the module critical section, whileset_completer()swaps the same field under the critical section withPy_XSETREF. On the free-threaded build a concurrent getter can be incrementing the refcount of a hook the setter is simultaneously dropping.get_pre_input_hook()has the same problem withstate->pre_input_hook— those are the only two hook fields with both aPy_XSETREF-based setter and a Python-level getter, so this covers the whole Python-visible getter surface.The fix adds
@critical_sectionto both getters, the same way gh-126895 did for the rest of the module.Verified with a free-threaded TSAN build (
--disable-gil --with-thread-sanitizer): the reproducer from the issue reports the race on main (thereadline_get_completerread vs theset_hookwrite) and runs clean with this change. The new tests in test_readline.py trigger the same TSAN reports without the fix and pass with it.Deliberately not included here:
get_begidx()/get_endidx()and the C callback paths (on_completion()etc.) read module state without the critical section too, but their writer/reader sides live in readline callbacks (flex_complete(),on_hook()), where taking the module critical section is a design question (it would serialize completion callbacks against all other readline calls) — that's a follow-up, not a one-line annotation.I used AI assistance while working on this change; I've reviewed it and can explain it.
readline.get_completer()readsstate->completerwithout the critical section #153291