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
bpo-36402: Fix threading._shutdown() race condition #13948
Conversation
This PR changes critical code, so I would appreciate multiple reviews :-) test_threading: test_threads_join_2() was added by commit 7b47699 in 2013, but the test failed randomly since it was added. It's just that failures were ignored until I created https://bugs.python.org/issue36402 last March. In fact, when the test failed randomly on buildbot (with tests run in parallel), it was fine since test_threading was re-run alone and then the test passed. The buildbot build was seen overall as a success. The test shows the bug using subinterpreters (Py_EndInterpreter), but I'm quite sure that Py_Finalize() has the same race condition since it also calls threading._shutdown(). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice find, and the fix looks correct. +1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't entirely digested this change yet. It looks good but I don't know if it is enough, I'll need to dig further to convince myself of that. Meanwhile, one issue noted.
When you're done making the requested changes, leave the comment: |
Sadly, these things are badly documented. The main issue is that there are 2 state changes:
Currently, _shutdown() rely on _active. But for Python finalization: Py_Finalize() and Py_EndInterpreter(), we really want to wait until the Python thread state get delete, not only when theading.Thread._bootstrap_inner() exits. test_threads_join_2() demonstrates the bug: "Fatal Python error: Py_EndInterpreter: not the last thread". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
I checked locally also with the patch in https://bugs.python.org/file48409/py_finalize.patch to test test_finalization_shutdown
manually.
Good catch! 🎉
@gpshead: Would yo mind to review the updated PR? |
Fix a race condition at Python shutdown when waiting for threads. Wait until the Python thread state of all non-daemon threads get deleted (join all non-daemon threads), rather than just wait until Python threads complete. * Add threading._shutdown_locks: set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() to wait until all Python thread states get deleted. See Thread._set_tstate_lock(). * Add also threading._shutdown_locks_lock to protect access to threading._shutdown_locks. * Add test_finalization_shutdown() test.
I rebased my PR and squashed commits. I clarified comments and the NEWS enty to specify that only non-daemon threads are joined. I reviewed my own PR and it LGTM :-) Since it already two approvals, I'm now comfortable enough to merge it. |
Thanks @vstinner for the PR 🌮🎉.. I'm working now to backport this PR to: 3.7, 3.8. |
Fix a race condition at Python shutdown when waiting for threads. Wait until the Python thread state of all non-daemon threads get deleted (join all non-daemon threads), rather than just wait until Python threads complete. * Add threading._shutdown_locks: set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() to wait until all Python thread states get deleted. See Thread._set_tstate_lock(). * Add also threading._shutdown_locks_lock to protect access to threading._shutdown_locks. * Add test_finalization_shutdown() test. (cherry picked from commit 468e5fe) Co-authored-by: Victor Stinner <vstinner@redhat.com>
GH-14033 is a backport of this pull request to the 3.8 branch. |
Fix a race condition at Python shutdown when waiting for threads. Wait until the Python thread state of all non-daemon threads get deleted (join all non-daemon threads), rather than just wait until Python threads complete. * Add threading._shutdown_locks: set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() to wait until all Python thread states get deleted. See Thread._set_tstate_lock(). * Add also threading._shutdown_locks_lock to protect access to threading._shutdown_locks. * Add test_finalization_shutdown() test. (cherry picked from commit 468e5fe) Co-authored-by: Victor Stinner <vstinner@redhat.com>
GH-14034 is a backport of this pull request to the 3.7 branch. |
…H-14050) * bpo-36402: Fix threading._shutdown() race condition (GH-13948) Fix a race condition at Python shutdown when waiting for threads. Wait until the Python thread state of all non-daemon threads get deleted (join all non-daemon threads), rather than just wait until Python threads complete. * Add threading._shutdown_locks: set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() to wait until all Python thread states get deleted. See Thread._set_tstate_lock(). * Add also threading._shutdown_locks_lock to protect access to threading._shutdown_locks. * Add test_finalization_shutdown() test. (cherry picked from commit 468e5fe) * bpo-36402: Fix threading.Thread._stop() (GH-14047) Remove the _tstate_lock from _shutdown_locks, don't remove None. (cherry picked from commit 6f75c87)
…3948) (pythonGH-14050) * bpo-36402: Fix threading._shutdown() race condition (pythonGH-13948) Fix a race condition at Python shutdown when waiting for threads. Wait until the Python thread state of all non-daemon threads get deleted (join all non-daemon threads), rather than just wait until Python threads complete. * Add threading._shutdown_locks: set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() to wait until all Python thread states get deleted. See Thread._set_tstate_lock(). * Add also threading._shutdown_locks_lock to protect access to threading._shutdown_locks. * Add test_finalization_shutdown() test. (cherry picked from commit 468e5fe) * bpo-36402: Fix threading.Thread._stop() (pythonGH-14047) Remove the _tstate_lock from _shutdown_locks, don't remove None. (cherry picked from commit 6f75c87) (cherry picked from commit e40a97a) Co-authored-by: Victor Stinner <vstinner@redhat.com>
) (GH-14054) * bpo-36402: Fix threading._shutdown() race condition (GH-13948) Fix a race condition at Python shutdown when waiting for threads. Wait until the Python thread state of all non-daemon threads get deleted (join all non-daemon threads), rather than just wait until Python threads complete. * Add threading._shutdown_locks: set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() to wait until all Python thread states get deleted. See Thread._set_tstate_lock(). * Add also threading._shutdown_locks_lock to protect access to threading._shutdown_locks. * Add test_finalization_shutdown() test. (cherry picked from commit 468e5fe) * bpo-36402: Fix threading.Thread._stop() (GH-14047) Remove the _tstate_lock from _shutdown_locks, don't remove None. (cherry picked from commit 6f75c87) (cherry picked from commit e40a97a) Co-authored-by: Victor Stinner <vstinner@redhat.com>
Fix a race condition at Python shutdown when waiting for threads. Wait until the Python thread state of all non-daemon threads get deleted (join all non-daemon threads), rather than just wait until Python threads complete. * Add threading._shutdown_locks: set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() to wait until all Python thread states get deleted. See Thread._set_tstate_lock(). * Add also threading._shutdown_locks_lock to protect access to threading._shutdown_locks. * Add test_finalization_shutdown() test.
This caused a regression for people overriding |
I guess that you are talking about this regression: https://bugs.python.org/issue37788 |
Fix a race condition at Python shutdown when waiting for threads. Wait until the Python thread state of all non-daemon threads get deleted (join all non-daemon threads), rather than just wait until Python threads complete. * Add threading._shutdown_locks: set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() to wait until all Python thread states get deleted. See Thread._set_tstate_lock(). * Add also threading._shutdown_locks_lock to protect access to threading._shutdown_locks. * Add test_finalization_shutdown() test.
Fix a race condition at Python shutdown when waiting for threads.
Wait until the Python thread state of threads get deleted, rather
than just wait until the Python thread completes.
of non-daemon threads used by _shutdown() wait until all Python
thread states get deleted (see Thread._set_tstate_lock()).
threading._shutdown_locks.
https://bugs.python.org/issue36402