From 7555d3a1f56bfff4d262df64cbb5dde49e1b361b Mon Sep 17 00:00:00 2001 From: Andrzej Skupien Date: Fri, 20 Feb 2026 15:41:52 +0100 Subject: [PATCH] threadtree: fix memory leak from thread ident reuse in pools the code was adding snapshots to the registry using thread.uuid as part of the key, then trying to remove them using thread.uuid again. when threadpoolexecutor reuses thread idents, thread.uuid can return a different value the second time because it looks up the uuid based on the thread's ident. this caused the cleanup to try removing a key that was never added, leaving the old entries in the registry forever. fix by capturing the uuid once and using that same value for both adding and removing --- easypy/threadtree.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/easypy/threadtree.py b/easypy/threadtree.py index df97fb1..1ffbbca 100644 --- a/easypy/threadtree.py +++ b/easypy/threadtree.py @@ -88,19 +88,22 @@ def start_new_thread(target, *args, **kwargs): def wrapper(*args, **kwargs): thread = threading.current_thread() + child_uuid = get_thread_uuid(thread) + registry_key = (parent_uuid, child_uuid) + if not DISABLE_ACROSS_THREADS: - _FRAME_SNAPSHOTS_REGISTRY[parent_thread.uuid, thread.uuid] = create_frame_snapshot(parent_frame, parent_thread) - uuid = get_thread_uuid(thread) - UUIDS_TREE[uuid] = parent_uuid + _FRAME_SNAPSHOTS_REGISTRY[registry_key] = create_frame_snapshot(parent_frame, parent_thread) + + UUIDS_TREE[child_uuid] = parent_uuid if _REGISTER_GREENLETS: IDENT_TO_GREENLET[thread.ident] = gevent.getcurrent() try: return target(*args, **kwargs) finally: # remove the snapshot connecting parent → this thread - _FRAME_SNAPSHOTS_REGISTRY.pop((parent_thread.uuid, thread.uuid), None) + _FRAME_SNAPSHOTS_REGISTRY.pop(registry_key, None) - IDENT_TO_UUID.pop(thread.ident) + IDENT_TO_UUID.pop(thread.ident, None) return _orig_start_new_thread(wrapper, *args, **kwargs)