diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index ac4e7a7e0f53b3..9ca273a5707092 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -762,6 +762,13 @@ 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 a thread which is not joined explicitly + # does not leak. Test written for reference leak checks. + def noop(): pass + with support.wait_threads_exit(): + threading.Thread(target=noop).start() + # Thread.join() is not called class ThreadJoinOnShutdown(BaseTestCase): diff --git a/Lib/threading.py b/Lib/threading.py index 813dae2aa9f8e5..84cda94024a569 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -745,6 +745,18 @@ def _newname(template="Thread-%d"): _shutdown_locks_lock = _allocate_lock() _shutdown_locks = set() + +def _maintain_shutdown_locks(): + """ + Drop any shutdown locks that don't correspond to running threads anymore. + Calling this from time to time avoids an ever-growing _shutdown_locks + set when Thread objects are not joined explicitly. See bpo-37788. + This must be called with _shutdown_locks_lock acquired. + """ + # If a lock was released, the corresponding thread has exited + to_remove = [lock for lock in _shutdown_locks if not lock.locked()] + _shutdown_locks.difference_update(to_remove) + # Main class for threads class Thread: @@ -910,6 +922,7 @@ def _set_tstate_lock(self): if not self.daemon: with _shutdown_locks_lock: + _maintain_shutdown_locks() _shutdown_locks.add(self._tstate_lock) def _bootstrap_inner(self): @@ -965,7 +978,8 @@ def _stop(self): self._tstate_lock = None if not self.daemon: with _shutdown_locks_lock: - _shutdown_locks.discard(lock) + # Remove our lock and other released locks from _shutdown_locks + _maintain_shutdown_locks() def _delete(self): "Remove current thread from the dict of currently running threads." diff --git a/Misc/NEWS.d/next/Library/2021-05-13-19-07-28.bpo-37788.adeFcf.rst b/Misc/NEWS.d/next/Library/2021-05-13-19-07-28.bpo-37788.adeFcf.rst new file mode 100644 index 00000000000000..0c33923e992452 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-05-13-19-07-28.bpo-37788.adeFcf.rst @@ -0,0 +1 @@ +Fix a reference leak when a Thread object is never joined.