Skip to content

gh-123961: make curses global flags free-threaded friendly #125000

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

Conversation

picnixz
Copy link
Member

@picnixz picnixz commented Oct 5, 2024

We allow global variables indicating whether the underlying curses functions have been called to be safely used in free-threaded builds.

In addition, since curses manages the screen, only one curses module can be loaded per process (i.e the exec() function is executed only once and an ImportError is raised if the user attempts to load multiple curses modules within the same process).

This can be tested via:

./python -c "import sys; import subprocess; subprocess.run(['./Programs/_testembed', 'test_repeated_init_exec', 'import curses'])"

@picnixz picnixz force-pushed the curses/globals-free-threading-friendly-123961 branch from ee22931 to 5a119c7 Compare October 5, 2024 08:39
@picnixz picnixz requested a review from vstinner October 5, 2024 12:03
Copy link
Member

@ZeroIntensity ZeroIntensity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find anything in here to complain about 😉, LGTM!

@vstinner
Copy link
Member

vstinner commented Oct 7, 2024

since curses manages the screen, only one curses module can be loaded per process

If you cannot have two instances of the _curses extension, what's the point of using atomic variables?

@picnixz
Copy link
Member Author

picnixz commented Oct 7, 2024

If you cannot have two instances of the _curses extension, what's the point of using atomic variables?

From what I understand, it'd be possible to access the curses extension state from different threads (not necessarily processes). Or am I wrong?


Ok I know why I didn't get the error in the terminal; it's because the module is still single-phase for now. Once it's multi-phase, I'll also get an error if I want to load it more than once per process (namely, I won't need to use the _testembed program).

@vstinner
Copy link
Member

vstinner commented Oct 7, 2024

I suggest to first make the _curses extension use the multiphase init API, and then look on how to make it safe to be used on a Free Threaded build.

@ZeroIntensity
Copy link
Member

Hmm, multiphase init shouldn't affect this. IIUC, module states need to be locked for free-threaded extensions.

Copy link
Contributor

@colesbury colesbury left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the curses module, but I'm a bit skeptical of this approach. Is curses thread safe?

static int
cursesmodule_exec(PyObject *module)
{
if (_Py_atomic_load_int(&curses_module_loaded)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The atomics here don't really do anything useful because you are not atomically checking and setting the variable together. Either use an atomic compare-exchange or don't use atomics at all.

Within an interpreter, the import lock will prevent multiple concurrent executions of cursesmodule_exec, but I'm not sure about what happens with multiple interpreters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, curses doesn't support subinterpreters, as indicated by the m_size being -1

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within an interpreter, the import lock will prevent multiple concurrent executions of cursesmodule_exec

Oh ok. Thanks for the tip. I wasn't really sure on this one. I'll revert this change later

@picnixz
Copy link
Member Author

picnixz commented Oct 7, 2024

I'm not familiar with the curses module, but I'm a bit skeptical of this approach. Is curses thread safe?

AFAIK, no (at least ncurses is NOT thread-safe). However, curses functions require some ncurses functions to be called at most once in order to work properly. We detect this by setting global flags.
So, I wondered whether it was correct or not to use locks so that those variables are set correctly.

I'm clearly not an expert in free-threading nor in concurrency in general, so I really appreciate your help!

@colesbury
Copy link
Contributor

However, curses functions require some ncurses functions to be called at most once in order to work properly. We detect this by setting global flags.

I think it's worth figuring out what sort of locking and thread-safety strategy we should use for curses before using atomic operations on the global variables. For example, if we want module-level locks for most of the APIs then we probably won't want to use atomic operations if the variables are only accessed from within the locks.

@picnixz
Copy link
Member Author

picnixz commented Oct 14, 2024

I think I'll close this PR for now. We'll likely need to revisit this question in a separate issue and PRs.

@picnixz picnixz closed this Oct 14, 2024
@picnixz picnixz deleted the curses/globals-free-threading-friendly-123961 branch October 14, 2024 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants