Skip to content
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-124622: Add PyGILState_EnsureOrFail() function #129688

Closed
wants to merge 4 commits into from

Conversation

vstinner
Copy link
Member

@vstinner vstinner commented Feb 5, 2025

Add new "OrFail" functions:

  • _PyEval_AcquireLockOrFail()
  • _PyEval_RestoreThreadOrFail()
  • _PyThreadState_AttachOrFail()
  • take_gil_or_fail()

📚 Documentation preview 📚: https://cpython-previews--129688.org.readthedocs.build/

Add new "OrFail" functions:

* _PyEval_AcquireLockOrFail()
* _PyEval_RestoreThreadOrFail()
* _PyThreadState_AttachOrFail()
* take_gil_or_fail()
@vstinner
Copy link
Member Author

vstinner commented Feb 5, 2025

@vstinner
Copy link
Member Author

vstinner commented Feb 5, 2025

cc @gpshead @ZeroIntensity

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.

As I said in the issue, I really don't want new GILState APIs, because they're quite broken for subinterpreters and misleading for free-threading (FT users sometimes think they don't need a tstate to use the C API).

I'd rather call this PyThreadState_Ensure, or something along those lines, and then deprecate PyGILState. cc @encukou

Comment on lines 1285 to 1286
Similar to :c:func:`PyGILState_Ensure`, but *state* is an argument
and return ``-1`` if the thread must exit. Return ``0`` on success.
Copy link
Member

Choose a reason for hiding this comment

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

This is confusing and misleading. The C or C++ thread must not exit. It's just that the function failed taking the GIL, probably because Python is finalized.

Copy link
Member

@pitrou pitrou left a comment

Choose a reason for hiding this comment

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

LGTM on the principle, but see comments below.

@wjakob
Copy link
Contributor

wjakob commented Feb 6, 2025

2cts from me: for this to be useful for extensions, it would be good if it is immediately part of the stable ABI. The implementation looks fine except for comment describing the function.

I would change it to:

This function resembles :c:func:`PyGILState_Ensure`, except that it returns with a status code even in the case of failure. 
Specifically, it returns ``0`` when the operation succeeded, and ``-1`` otherwise. In contrast to :c:func:`PyGILState_Ensure`, *state* is an argument. 

In the case of failure, it is *unsafe* to use the Python API following the call. Releasing the obtained *state* via :c:func:`PyGILState_Release` should only be done in the case of success.

@vstinner vstinner requested review from a team and encukou as code owners February 6, 2025 10:10
@vstinner
Copy link
Member Author

vstinner commented Feb 6, 2025

for this to be useful for extensions, it would be good if it is immediately part of the stable ABI.

Ok, done.

The implementation looks fine except for comment describing the function. I would change it to: (...)

Done.

@vstinner
Copy link
Member Author

vstinner commented Feb 6, 2025

I'd rather call this PyThreadState_Ensure, or something along those lines, and then deprecate PyGILState. cc @encukou

If we adopt a different API, I would suggest avoiding also the enum, use a simple int instead.

API: int PyThreadState_Ensure(void):

  • On success, return a state >= 0 which should be passed to PyThreadState_Release().
  • On error, return -1.

API: void PyThreadState_Release(int state)

Usage:

int state = PyThreadState_Ensure();
if (state < 0) return;

// ... use the Python C API ...

PyThreadState_Release(state);

@encukou
Copy link
Member

encukou commented Feb 6, 2025

I'd rather call this PyThreadState_Ensure, or something along those lines, and then deprecate PyGILState

I'd also prefer that, but AFAIK such a function would need an extra argument (to specify which interpreter you want to end up in). Is that right?
Extensions would need to pass that value through their callback mechanisms.

As an incremental improvement for current users of PyGILState_Ensure, this looks good. But let's not close the issue with this PR.

Could you add the mention that these are “not compatible with sub-interpreters” directly to the docs?

This should go through the C API WG.

@vstinner
Copy link
Member Author

vstinner commented Feb 6, 2025

Could you add the mention that these are “not compatible with sub-interpreters” directly to the docs?

Which API should be recommended instead, if this one is not compatible with sub-interpreters?

@ZeroIntensity
Copy link
Member

PyThreadState_Swap, or anything of that nature.

@vstinner
Copy link
Member Author

vstinner commented Feb 7, 2025

PyThreadState_Swap, or anything of that nature.

PyThreadState_Swap() requires a tstate. How do you create the tstate in a new thread to support sub-interpreters?

@ZeroIntensity
Copy link
Member

Py_NewInterpreter or Py_NewInterpreterFromConfig, but creating the interpreter isn't the problem with PyGILState. The issue is that if you use PyGILState_Ensure in a thread that was created by a subinterpreter, then that thread will have the GIL of the main interpreter, not the subinterpreter. That means the thread can't safely interact with the subinterpreter in any way, but it will just end up racing instead of failing, which is really bad.

I think turning this API into PyThreadState_Ensure (along with deprecating PyGILState) would be good incentive for people to switch in 3.14. Obligatory ping @ericsnowcurrently

@pitrou
Copy link
Member

pitrou commented Feb 11, 2025

Indeed it might be a good idea to add:

int PyThreadState_Ensure(PyInterpreterState*);
void PyThreadState_Release(int state, PyInterpreterState*);

@ZeroIntensity
Copy link
Member

An int handle is probably extra work. Why not just advise use of PyThreadState_DeleteCurrent?

@vstinner
Copy link
Member Author

I created #130012 to add PyThreadState_Ensure() and PyThreadState_Release() functions.

In the case of failure, it is *unsafe* to use the Python API following the
call. Releasing the obtained *state* via :c:func:`PyGILState_Release` should
only be done in the case of success.

Copy link
Member

Choose a reason for hiding this comment

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

Could you add the mention that these are “not compatible with sub-interpreters” directly to the docs?
Which API should be recommended instead, if this one is not compatible with sub-interpreters?

I meant repeating the note above PyGILState_Ensure that says The following functions use thread-local storage, and are not compatible with sub-interpreters:. Users that click the What's New link go directly to the function docs and are quite likely to miss the warning.
Something like:

Suggested change
Like :c:func:`!PyGILState_Ensure`, this function is not safe to use with
supinterpreters, since it is not possible to specify which interpreter
is activated.

Copy link
Member Author

Choose a reason for hiding this comment

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

#130012 PR should be subinterpreter-friendly, I closed this PR instead.

Comment on lines +1287 to +1288
operation succeeded, and ``-1`` otherwise. In contrast to
:c:func:`PyGILState_Ensure`, *state* is an argument.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
operation succeeded, and ``-1`` otherwise. In contrast to
:c:func:`PyGILState_Ensure`, *state* is an argument.
operation succeeded, and ``-1`` otherwise.
The return value of :c:func:`!PyGILState_Ensure`
is stored in *\*state*.

:c:func:`PyGILState_Ensure`, *state* is an argument.

In the case of failure, it is *unsafe* to use the Python API following the
call. Releasing the obtained *state* via :c:func:`PyGILState_Release` should
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
call. Releasing the obtained *state* via :c:func:`PyGILState_Release` should
call. Releasing the obtained *state* via :c:func:`PyGILState_Release` must

Copy link
Member Author

Choose a reason for hiding this comment

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

I applied this suggestion to #130012 since I closed this PR.

@vstinner
Copy link
Member Author

I created #130012 to add PyThreadState_Ensure() and PyThreadState_Release() functions.

It seems like the trend is more towards passing interp explicitly, so I close this PR.

@vstinner vstinner closed this Feb 18, 2025
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.

5 participants