bpo-36402: Fix threading._shutdown() race condition#13948
bpo-36402: Fix threading._shutdown() race condition#13948vstinner merged 1 commit intopython:masterfrom vstinner:threading_shutdown
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(). |
pitrou
left a comment
There was a problem hiding this comment.
Nice find, and the fix looks correct. +1.
gpshead
left a comment
There was a problem hiding this comment.
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". |
pablogsal
left a comment
There was a problem hiding this comment.
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