From 33ba29de38edcc27c00cd51c1f583efc35530c1c Mon Sep 17 00:00:00 2001 From: hanxinke Date: Fri, 2 Apr 2021 18:07:50 -0400 Subject: [PATCH 1/2] bpo-37788: Fix continuous memory leak occurs when multiple subthreads which are not joined are started. --- Lib/test/test_threading.py | 12 ++++++++++++ Lib/threading.py | 20 +++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) 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: From 9a45081a413d4a2552e5ee4608b727461b390b16 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 2 Apr 2021 11:56:51 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2021-04-02-11-56-50.bpo-37788.jbaJGP.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2021-04-02-11-56-50.bpo-37788.jbaJGP.rst 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