New submission from Adam <adam.jgr...@gmail.com>:
When running 3.7, we noticed a memory leak in threading._shutdown_locks when non-deamon threads are started but "join()" or "is_alive()" is never called. Here's a test to illustrate the growth: ========= import threading import time import tracemalloc def test_leaking_locks(): tracemalloc.start(10) snap1 = tracemalloc.take_snapshot() def print_things(): print('.', end='') for x in range(500): t = threading.Thread(target=print_things) t.start() time.sleep(5) print('') gc.collect() snap2 = tracemalloc.take_snapshot() filters = [] for stat in snap2.filter_traces(filters).compare_to(snap1.filter_traces(filters), 'traceback')[:10]: print("New Bytes: {}\tTotal Bytes {}\tNew blocks: {}\tTotal blocks: {}: ".format(stat.size_diff, stat.size, stat.count_diff ,stat.count)) for line in stat.traceback.format(): print(line) ========= ========= Output in v3.6.8: New Bytes: 840 Total Bytes 840 New blocks: 1 Total blocks: 1: File "/usr/local/lib/python3.6/threading.py", line 884 self._bootstrap_inner() New Bytes: 608 Total Bytes 608 New blocks: 4 Total blocks: 4: File "/usr/local/lib/python3.6/tracemalloc.py", line 387 self.traces = _Traces(traces) File "/usr/local/lib/python3.6/tracemalloc.py", line 524 return Snapshot(traces, traceback_limit) File "/gems/tests/integration/endpoint_connection_test.py", line 856 snap1 = tracemalloc.take_snapshot() File "/usr/local/lib/python3.6/site-packages/_pytest/python.py", line 198 testfunction(**testargs) File "/usr/local/lib/python3.6/site-packages/pluggy/callers.py", line 187 res = hook_impl.function(*args) File "/usr/local/lib/python3.6/site-packages/pluggy/manager.py", line 87 firstresult=hook.spec.opts.get("firstresult") if hook.spec else False, File "/usr/local/lib/python3.6/site-packages/pluggy/manager.py", line 93 return self._inner_hookexec(hook, methods, kwargs) File "/usr/local/lib/python3.6/site-packages/pluggy/hooks.py", line 286 return self._hookexec(self, self.get_hookimpls(), kwargs) File "/usr/local/lib/python3.6/site-packages/_pytest/python.py", line 1459 self.ihook.pytest_pyfunc_call(pyfuncitem=self) File "/usr/local/lib/python3.6/site-packages/_pytest/runner.py", line 111 item.runtest() ========== Output in v3.7.4: New Bytes: 36000 Total Bytes 36000 New blocks: 1000 Total blocks: 1000: File "/usr/local/lib/python3.7/threading.py", line 890 self._bootstrap_inner() File "/usr/local/lib/python3.7/threading.py", line 914 self._set_tstate_lock() File "/usr/local/lib/python3.7/threading.py", line 904 self._tstate_lock = _set_sentinel() New Bytes: 32768 Total Bytes 32768 New blocks: 1 Total blocks: 1: File "/usr/local/lib/python3.7/threading.py", line 890 self._bootstrap_inner() File "/usr/local/lib/python3.7/threading.py", line 914 self._set_tstate_lock() File "/usr/local/lib/python3.7/threading.py", line 909 _shutdown_locks.add(self._tstate_lock) ================= It looks like this commit didn't take into account the tstate_lock cleanup that happens in the C code, and it's not removing the _tstate_lock of completed threads from the _shutdown_locks once the thread finishes, unless the code manually calls "join()" or "is_alive()" on the thread: https://github.com/python/cpython/commit/468e5fec8a2f534f1685d59da3ca4fad425c38dd Let me know if I can provide more clarity on this! ---------- messages: 358551 nosy: krypticus priority: normal severity: normal status: open title: Threading memory leak in _shutdown_locks for non-daemon threads type: resource usage versions: Python 3.7, Python 3.8, Python 3.9 _______________________________________ Python tracker <rep...@bugs.python.org> <https://bugs.python.org/issue39074> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com