mirror of https://github.com/python/cpython
bpo-37788: Fix reference leak when Thread is never joined (GH-26103)
When a Thread is not joined after it has stopped, its lock may remain in the _shutdown_locks set until interpreter shutdown. If many threads are created this way, the _shutdown_locks set could therefore grow endlessly. To avoid such a situation, purge expired locks each time a new one is added or removed.
This commit is contained in:
parent
07797121cc
commit
c10c2ec7a0
|
@ -907,6 +907,13 @@ class ThreadTests(BaseTestCase):
|
|||
thread.join()
|
||||
self.assertTrue(target.ran)
|
||||
|
||||
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 threading_helper.wait_threads_exit():
|
||||
threading.Thread(target=noop).start()
|
||||
# Thread.join() is not called
|
||||
|
||||
|
||||
class ThreadJoinOnShutdown(BaseTestCase):
|
||||
|
|
|
@ -780,12 +780,27 @@ _active_limbo_lock = _allocate_lock()
|
|||
_active = {} # maps thread id to Thread object
|
||||
_limbo = {}
|
||||
_dangling = WeakSet()
|
||||
|
||||
# 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().
|
||||
_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:
|
||||
|
@ -968,6 +983,7 @@ class Thread:
|
|||
|
||||
if not self.daemon:
|
||||
with _shutdown_locks_lock:
|
||||
_maintain_shutdown_locks()
|
||||
_shutdown_locks.add(self._tstate_lock)
|
||||
|
||||
def _bootstrap_inner(self):
|
||||
|
@ -1023,7 +1039,8 @@ class Thread:
|
|||
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."
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fix a reference leak when a Thread object is never joined.
|
Loading…
Reference in New Issue