44import contextlib
55import functools
66import inspect
7+ import threading
78from collections .abc import AsyncGenerator , Awaitable , Callable , Generator
89from typing import TypeVar , overload
910
2021 LeakAction = None # pyright: ignore[reportAssignmentType]
2122
2223
24+ # Thread-local storage to track if monitoring is already active
25+ _thread_local = threading .local ()
26+
27+
2328def is_pyleak_enabled () -> bool :
2429 """Check if PyLeak monitoring is enabled and available.
2530
@@ -43,18 +48,28 @@ def monitor_sync():
4348 yield
4449 return
4550
51+ # Check if monitoring is already active in this thread
52+ if getattr (_thread_local , "monitoring_active" , False ):
53+ yield
54+ return
55+
4656 config = get_config ()
4757 action = config .pyleak_action or LeakAction .WARN # pyright: ignore[reportOptionalMemberAccess]
4858
49- with contextlib .ExitStack () as stack :
50- # Thread leak detection has issues with background tasks (no_thread_leaks)
51- stack .enter_context (
52- no_event_loop_blocking ( # pyright: ignore[reportOptionalCall]
53- action = action ,
54- threshold = config .pyleak_blocking_threshold ,
59+ # Mark monitoring as active
60+ _thread_local .monitoring_active = True
61+ try :
62+ with contextlib .ExitStack () as stack :
63+ # Thread leak detection has issues with background tasks (no_thread_leaks)
64+ stack .enter_context (
65+ no_event_loop_blocking ( # pyright: ignore[reportOptionalCall]
66+ action = action ,
67+ threshold = config .pyleak_blocking_threshold ,
68+ )
5569 )
56- )
57- yield
70+ yield
71+ finally :
72+ _thread_local .monitoring_active = False
5873
5974
6075@contextlib .asynccontextmanager
@@ -68,23 +83,33 @@ async def monitor_async():
6883 yield
6984 return
7085
86+ # Check if monitoring is already active in this thread
87+ if getattr (_thread_local , "monitoring_active" , False ):
88+ yield
89+ return
90+
7191 config = get_config ()
7292 action = config .pyleak_action or LeakAction .WARN # pyright: ignore[reportOptionalMemberAccess]
7393
74- async with contextlib .AsyncExitStack () as stack :
75- # Thread leak detection has issues with background tasks (no_thread_leaks)
76- # Re-add thread leak later.
77-
78- # Block detection for event loops
79- stack .enter_context (
80- no_event_loop_blocking ( # pyright: ignore[reportOptionalCall]
81- action = action ,
82- threshold = config .pyleak_blocking_threshold ,
94+ # Mark monitoring as active
95+ _thread_local .monitoring_active = True
96+ try :
97+ async with contextlib .AsyncExitStack () as stack :
98+ # Thread leak detection has issues with background tasks (no_thread_leaks)
99+ # Re-add thread leak later.
100+
101+ # Block detection for event loops
102+ stack .enter_context (
103+ no_event_loop_blocking ( # pyright: ignore[reportOptionalCall]
104+ action = action ,
105+ threshold = config .pyleak_blocking_threshold ,
106+ )
83107 )
84- )
85- # Task leak detection has issues with background tasks (no_task_leaks)
108+ # Task leak detection has issues with background tasks (no_task_leaks)
86109
87- yield
110+ yield
111+ finally :
112+ _thread_local .monitoring_active = False
88113
89114
90115YieldType = TypeVar ("YieldType" )
0 commit comments