summaryrefslogtreecommitdiffstats
path: root/Lib/threading.py
diff options
context:
space:
mode:
authorAntoine Pitrou <antoine@python.org>2021-05-14 19:37:20 (GMT)
committerGitHub <noreply@github.com>2021-05-14 19:37:20 (GMT)
commitc10c2ec7a0e06975e8010c56c9c3270f8ea322ec (patch)
tree7fa4d274bd1bd9d09bff60b50b59d25a2ac28b92 /Lib/threading.py
parent07797121cc290ede0b3d3cf02068f3d993cddd15 (diff)
downloadcpython-c10c2ec7a0e06975e8010c56c9c3270f8ea322ec.zip
cpython-c10c2ec7a0e06975e8010c56c9c3270f8ea322ec.tar.gz
cpython-c10c2ec7a0e06975e8010c56c9c3270f8ea322ec.tar.bz2
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.
Diffstat (limited to 'Lib/threading.py')
-rw-r--r--Lib/threading.py19
1 files changed, 18 insertions, 1 deletions
diff --git a/Lib/threading.py b/Lib/threading.py
index fb70abd..6c3d49c 100644
--- a/Lib/threading.py
+++ b/Lib/threading.py
@@ -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."