-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-126895: fix readline module in free-threaded build #131208
Conversation
ping @ZeroIntensity , @colesbury |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This covers free-threading, but after re-reading some of my analysis from back in November, I'm pretty sure it's the GNU readline library itself that's not thread-safe. Critical sections are per-interpreter, so this will continue to race in subinterpreters. I'd rather handle both cases in one fix.
That said, I'm not sure whether it's unsafe to call any readline
function concurrently, or just the same function across multiple threads. I suspect its the former. In that case, let's go with a global lock around readline calls, but otherwise we can go with a local static PyMutex
to serialize single calls.
What I meant above is that
You can call across threads and it won't crash because it is synchronized with GIL, you will just get wrong results. But if you called free-threaded it would crash, now it just behaves like not free-threaded and doesn't crash.
Could do that, but is that safe if one call dies in an unexpected way and mutex is left locked? |
|
The single lock on
I was under the impression that critical sections, as opposed to pure "Critical sections avoid deadlocks by implicitly suspending active critical sections and releasing the locks during calls to PyEval_SaveThread(). When PyEval_RestoreThread() is called, the most recent critical section is resumed, and its locks reacquired. This means the critical section API provides weaker guarantees than traditional locks ..." |
Critical sections are based off an object's |
So what's the verdict then wrt this, changes? |
Let's just confirm that we really do need a global lock here; I don't want to drastically inhibit scaling if we don't have to. |
Short: Yes it needs to be global because readline is not thread-safe. But its only applied on things that actually call into readline. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding critical sections seems fine to me:
- I don't think the new tests are worth it
- There are a bunch of functions that don't have critical sections: readline.write_history_file, readline.append_history_file, readline.set_history_length, ...
I'm fine with critical sections too, I'm just worried about basically enabling the GIL for all usage of
I think you're misinterpreting what I mean a little. "Thread-safety" is a blanket term; I mean that we might not need locks synchronizing the entire module. For example, it makes sense to me that |
Adding a lock to the readline module is not like enabling the GIL. The GIL affects everything in the process, including things that don't touch the readline module. The readline lock only affects things that use readline. Using multiple threads to concurrently muck around with readline is not a real use case that we should optimize for. So far the only time I've seen anyone try to use readline from multiple threads at all is when fuzzing CPython.
In general, we should prefer the simplest approach to thread safety that is efficient. In this case, that means a single lock for the entire readline module. |
One potential issue. |
|
🤖 New build scheduled with the buildbot fleet by @colesbury for commit 0dca261 🤖 Results will be shown at: https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F131208%2Fmerge If you want to schedule another build, you need to add the 🔨 test-with-refleak-buildbots label again. |
Thanks Tom! I'm not going to backport this to 3.13. I think it's unlikely that anyone is going to try to use the readline module from multiple threads concurrently outside of fuzzing. |
…31208) The underlying readline library is not thread-safe so this adds `@critical_section` to functions that use it.
This is nothing more than adding
@critical_section
to functions which use the readline library. It is not thread-safe (because readline is not thread-safe), but it doesn't crash like before.The test
test_free_threading_doctest_difflib
is probably overkill, but included at first to show that it works. Can comment out or remove as desired.Also, without
test_free_threading_doctest_difflib
(that uses stuff which is not free-thread safe),test_readline
fails without crashing with--parallel-threads
, where previously it crashed. Also without it doesn't complain when tested with TSAN.Subinterpreters are not an issue because
readline
just doesn't load in them:module readline does not support loading in subinterpreters
readline.set_completer_delims
in threads in a free-threaded build #126895