Skip to content

Avoid thread termination in scoped_released #2657

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

Merged
merged 9 commits into from
Dec 19, 2020
Merged
Changes from 1 commit
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
12 changes: 12 additions & 0 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -2121,7 +2121,12 @@ class gil_scoped_acquire {
pybind11_fail("scoped_acquire::dec_ref(): internal error!");
#endif
PyThreadState_Clear(tstate);
#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 6) && !defined(Py_LIMITED_API)
if (!_Py_IsFinalizing())
PyThreadState_DeleteCurrent();
#else
PyThreadState_DeleteCurrent();
#endif
PYBIND11_TLS_DELETE_VALUE(detail::get_internals().tstate);
release = false;
}
Expand Down Expand Up @@ -2153,7 +2158,14 @@ class gil_scoped_release {
~gil_scoped_release() {
if (!tstate)
return;
#if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 6) && !defined(Py_LIMITED_API)
// PyEval_RestoreThread() should not be called if runtime is finilizing
// See https://docs.python.org/3/c-api/init.html#c.PyEval_RestoreThread
if (!_Py_IsFinalizing())
PyEval_RestoreThread(tstate);
#else
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor: typo: finilizing

More significantly: I think the code could be made more readable and maintainable by centralizing the #if #else #endif in a, e.g., detail::finalization_guard() inline helper function, so that there is a formal link to the comment from both places, and less preprocessor clutter.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure that that's not just going to add more confusion, to have another indirection? As long as it's just these two (very related) spots, to me, this seem reasonably fine? (We currently have these kinds of things inline in other places as well, and this should make it easy to remove once we drop <= 3.6?)

Copy link
Collaborator

Choose a reason for hiding this comment

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

The practical version of DRY/SPOT that I've seen states that exactly one duplication is allowed, if it's clearly more complex to combine it. Trying to take DRY/SPOT to the extreme of truly one point of truth for all code can impose an unmanageable level of complexity, so allowing it to be relaxed by one at programmer discretion balances the ideal with practicality. (and if there's more than one duplication, the extra complexity is always worth it).

In short, I'm fine either way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks Henry! I hadn't heard of DRY/SPOT before, good to know there is something to refer to.

In this particular case I'm not so much concerned about the code duplication, but mostly about the comment only appearing in one place. For someone arriving at the second place from some completely different context, they will not even know the comment exists in the other place, and may miss it. But if they get curious about detail::finalization_guard(), they are guaranteed to find the comment.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The comment is true, though! I completely agree there. There should at least be a "see a few lines up/down, at ...".

Apart from that, I don't feel too strongly, so up to @malfet AFAIC, but I just though the #if SOME_PYTHON_VERSION pattern exists inline in lots of places of code.
And it's kind of hard to capture what it's doing (i.e., finalization_guard doesn't really describe what's happening) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the feedback. Fixed typo and moved and extended runtime finalization check to pybind11/detail/internals.h

PyEval_RestoreThread(tstate);
#endif
if (disassoc) {
auto key = detail::get_internals().tstate;
PYBIND11_TLS_REPLACE_VALUE(key, tstate);
Expand Down