diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 36d9fbb162e215..c3717e544bdfaa 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -757,6 +757,18 @@ def test_shutdown_locks(self): # Daemon threads must never add it to _shutdown_locks. self.assertNotIn(tstate_lock, threading._shutdown_locks) + def test_leak_without_join(self): + # bpo-37788: Test that no continuous memory leak occurs when multiple + # subthreads which are not joined are started. + def noop(): pass + for i in range(10): + thread = threading.Thread(target=noop) + thread.start() + self.assertIn(thread._tstate_lock, threading._shutdown_locks) + while thread._tstate_lock.locked(): + pass + self.assertLess(len(threading._shutdown_locks), 10) + class ThreadJoinOnShutdown(BaseTestCase): diff --git a/Lib/threading.py b/Lib/threading.py index b961456fb792a2..71fdc3fe64d9fd 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -896,6 +896,21 @@ def _bootstrap(self): def _set_ident(self): self._ident = get_ident() + def _discard_tstate_locks(self): + with _shutdown_locks_lock: + locks = list(_shutdown_locks) + + if not locks: + return + + for lock in locks: + if lock.locked(): + continue + lock.acquire() + lock.release() + with _shutdown_locks_lock: + _shutdown_locks.discard(lock) + def _set_tstate_lock(self): """ Set a lock object which will be released by the interpreter when @@ -964,7 +979,10 @@ def _bootstrap_inner(self): # the exception keeps the target alive past when we # assert that it's dead. #XXX self._exc_clear() - pass + # To prevent continuous leakage of tstate locks, try to discard at + # the end of each subthread. + if not self.daemon: + self._discard_tstate_locks() finally: with _active_limbo_lock: try: diff --git a/Misc/NEWS.d/next/Library/2021-04-02-11-56-50.bpo-37788.jbaJGP.rst b/Misc/NEWS.d/next/Library/2021-04-02-11-56-50.bpo-37788.jbaJGP.rst new file mode 100644 index 00000000000000..98a184d4d67be9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-04-02-11-56-50.bpo-37788.jbaJGP.rst @@ -0,0 +1 @@ +Add threading.Thread._discard_tstate_locks() method to check whether the lock of other subthreads can be deleted from the _shutdown_locks list when the subthread task ends and prevent continuous leakage of tstate locks. \ No newline at end of file