Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-87135: threading.Lock: Raise rather than hang on Python finalization#135991

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
encukou wants to merge1 commit intopython:main
base:main
Choose a base branch
Loading
fromencukou:no-acquire-in-finalize

Conversation

encukou
Copy link
Member

@encukouencukou commentedJun 26, 2025
edited by bedevere-appbot
Loading

After Python finalization gets to the point where no other thread can attach thread state, attempting to acquire a Python lock will hang.
Raise PythonFinalizationError instead of hanging.

@colesbury, does this look maintainable (and correct) to you?

…lizationAfter Python finalization gets to the point where no other threadcan attach thread state, attempting to acquire a Python lock musthang.Raise PythonFinalizationError instead of hanging.
Copy link
Contributor

@colesburycolesbury left a comment

Choose a reason for hiding this comment

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

I think this approach makes sense. A comment about the flags below

@@ -95,6 +95,18 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
if (timeout == 0) {
return PY_LOCK_FAILURE;
}
if ((flags & _PY_LOCK_PYTHONLOCK) && Py_IsFinalizing()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The first part of the condition here is checking if any of the bits of_PY_LOCK_PYTHONLOCK are set. In other words, it will pass even if only_PY_LOCK_DETACH is set fromPyMutex_Lock().

It could be:

if ((flags & _PY_LOCK_PYTHONLOCK) == _PY_LOCK_PYTHONLOCK && Py_IsFinalizing()) {

But given that this is an easy mistake to make and hard to spot, I'd rather just have a separate bit for fail-on-finalization and check that individual bit here.

If we want a constant to combine those three bits, let's keep that constant it in_threadmodule.c.

encukou reacted with heart emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

🤦

Choose a reason for hiding this comment

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

Won't this be prone to TOCTOU issues?Py_IsFinalizing could pass, but once the thread state detaches in the parking lot, the interpreter is free to start finalizing. The parking lot will then hang the thread upon reattachment in_PySemaphore_Wait, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

No - this check is only relevant for the main thread (the one that performsPy_Finalize()) so there's no TOCTOU. There are two "hanging" threads behaviors that you may be confusing:

  1. Non-main threads, typically daemon threads (and occasionally other non-daemon threads), hang when they try to attach during Python finalization. This PR doesn't affect that behavior.
  2. The main thread, when performing finalization, may acquirethreading.Lock/RLock called via destructors / GC. If those locks happen to be held by other (now hung) threads, then the shutdown would deadlock. This instead raises a Python exception.

Choose a reason for hiding this comment

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

Hm, yeah, I was confusing it with that. But, you've mentioned before that acquiring Python locks in finalizers isn't safe regardless of finalization, because the GC might run in any code where the lock is held and deadlock while reacquiring it. Looking at the test case, I think that still applies. What's the use-case that this PR is fixing?

@vstinner
Copy link
Member

Tests / Windows (free-threading) / Build and test (arm64) (pull_request)
Tests / Windows (free-threading) / Build and test (x64) (pull_request)

There is a suspicious error when running test.test_multiprocessing_spawn.test_processes:

Assertion failed: !PyErr_Occurred(), file D:\a\cpython\cpython\Python\specialize.c, line 740Fatal Python error: Aborted<Cannot show all threads while the GIL is disabled>Stack (most recent call first):  File "D:\a\cpython\cpython\Lib\logging\__init__.py", line 891 in _removeHandlerRefCurrent thread's C stack trace (most recent call first):  <cannot get C stack on this system>

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@colesburycolesburycolesbury left review comments

@ZeroIntensityZeroIntensityZeroIntensity left review comments

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

4 participants
@encukou@vstinner@colesbury@ZeroIntensity

[8]ページ先頭

©2009-2025 Movatter.jp