Skip to content

bpo-36475: Finalize PyEval_AcquireLock() and PyEval_AcquireThread() properly #12667

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 7 commits into from
Apr 29, 2019

Conversation

nanjekyejoannah
Copy link
Contributor

@nanjekyejoannah nanjekyejoannah commented Apr 2, 2019

I have added changes to finalize finalize PyEval_AcquireLock() and PyEval_AcquireThread() properly.

https://bugs.python.org/issue36475

@nanjekyejoannah
Copy link
Contributor Author

cc @ericsnowcurrently , @ncoghlan .

Copy link
Contributor

@ncoghlan ncoghlan left a comment

Choose a reason for hiding this comment

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

LGTM, but I'd like Eric to take a look as well. In particular, I'm not sure if we have any existing test cases for this behaviour, and whether it might make sense to add one to the embedding tests.

@vstinner vstinner requested review from pablogsal and pitrou April 24, 2019 13:27
@vstinner
Copy link
Member

@pablogsal @pitrou: Would you mind to review this change? It seems like you know the modified functions :-)

@pablogsal
Copy link
Member

pablogsal commented Apr 24, 2019

I would recommend adding a note in the doc for these functions as we did in fde9b33 .

Also, this code appears 3 times now in ceval.c so we could refactor this into a macro/inline function.

@pitrou
Copy link
Member

pitrou commented Apr 24, 2019

Hmm... this will exit any thread during finalization, not just daemon threads? I'm not sure why this is a good idea.

@nanjekyejoannah
Copy link
Contributor Author

I have made the requested changes; please review again.

@bedevere-bot
Copy link

Thanks for making the requested changes!

@ncoghlan: please review the changes made to this pull request.

@ncoghlan
Copy link
Contributor

@pitrou PyEval_RestoreThread, and hence the Py_END_ALLOW_THREADS macro and the GILState APIs, already cause all threads to exit if they find the interpreter is already finalizing, but the interpreter joins all of its own non-daemon threads before it marks itself as finalizing:

Py_FinalizeEx(void)

This PR just makes the lower level GIL acquisition APIs behave the same way PyEval_RestoreThread already does so that surviving daemon threads will exit rather than risking deadlocking based on exactly which API they call.

This does highlight that the change is worth mentioning in the porting section of the Python 3.8 What's New, though, along with "versionchanged" notes on the affected APIs.

@pitrou
Copy link
Member

pitrou commented Apr 25, 2019

I see. I'm always wary of the potential issues with such low-level changes, but you're right that we're already half way to the behaviour established by the PR, so I guess it's fine.

pitrou
pitrou previously requested changes Apr 25, 2019
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.

I guess it's ok on the principle. Please see comments below.

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

Copy link
Contributor

@ncoghlan ncoghlan left a comment

Choose a reason for hiding this comment

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

The core functionality of the change still looks good to me, just some refactoring suggestions for the code, and a couple more documentation tweaks.

My proposed wording for the NEWS entry can be re-used as a bullet point in the What's New porting section, with an extra sentence on what to do about it:

PyEval_AcquireLock and PyEval_AcquireThread now terminate the current thread if called while the interpreter is finalizing, making them consistent with PyEval_RestoreThread, Py_END_ALLOW_THREADS, and PyGILState_Ensure. If this behaviour is not desired, guard the call by checking _Py_IsFinalizing or sys.is_finalizing.

.. note::
Calling this function from a thread when the runtime is finalizing
will terminate the thread, even if the thread was not created by Python.
You can use :c:func:`_Py_IsFinalizing` or :func:`sys.is_finalizing` to
Copy link
Contributor

Choose a reason for hiding this comment

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

@vstinner Are you aware of any particular reason for _Py_IsFinalizing being a nominally private API? If we're recommending that folks call it in the documentation, that's a pretty strong sign that it should really be public.

(That can be broken out to a separate issue, though - it doesn't need to be resolved here)

Copy link
Member

Choose a reason for hiding this comment

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

I don't know. Feel free to open an issue to propose to make it public if you want.

In my PR #12934, I modified the internal _Py_CURRENTLY_FINALIZING() macro to add a runtime parameter :-) (PR currently under review.)

@nanjekyejoannah
Copy link
Contributor Author

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@pitrou, @ncoghlan: please review the changes made to this pull request.

@vstinner
Copy link
Member

FYI I wrote PR #12453 "Destroy the GIL at exit".

Copy link
Contributor

@ncoghlan ncoghlan left a comment

Choose a reason for hiding this comment

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

This looks good to me now (noting the follow-up PR in #12679 to switch the thread cleanup to be per-interpreter rather than per-runtime)

:c:func:`PyEval_RestoreThread` is a higher-level function which is always
available (even when threads have not been initialized).

.. note::
Copy link
Contributor

Choose a reason for hiding this comment

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

Ended up with two copies of this note.

@nanjekyejoannah
Copy link
Contributor Author

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@pitrou, @ncoghlan: please review the changes made to this pull request.

@vstinner vstinner dismissed pitrou’s stale review April 29, 2019 08:25

Joannah addressed Antoine's comments.

@vstinner vstinner merged commit f781d20 into python:master Apr 29, 2019
@vstinner
Copy link
Member

I am very scared by this change touching critical parts of Python, but I'm somehow confident that it's the right thing to do.

Antoine: Py_Finalize() explicitly waits until all non-daemon threads complete, so this change should impact "regular" threads: only daemon threads.

Note: I have no idea of the impact on performances, but IMHO correctness matters more than performance here. If there is an overhead, I expect it to be not significant on macro benchmarks, and low on micro-benchmarks.

--

Side note. I really hate the whole concept of daemon threads. Kill a thread anytime is just a very high risk of inconsistency. The thread can be killed while writing in the middle of a file, it can leave temporary files, etc. I would prefer to remove the concept of daemon threads and require developers to call pthread_kill() to make them understand what they are doing (something stupid IMHO :-)). Daemon threads make Python finalization very fragile, but the GIL + this PR make the finalization less fragile. Hint: search for "daemon" in Google Image. IMHO the name is well chosen.

@vstinner
Copy link
Member

I am not comfortable to backport this change to Python 3.7. It's too early to know how it will impact applications and how many complains we will get :-) If someone really wants to backport this scary change to 3.7, I would suggest to wait for 1 month after Python 3.8.0 final release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants