@@ -31,6 +31,7 @@ def __init__(
3131 self ._record_lost_func = record_lost_func
3232 self ._running = True
3333 self ._lock = threading .Lock ()
34+ self ._active : "threading.local" = threading .local ()
3435
3536 self ._flush_event : "threading.Event" = threading .Event ()
3637
@@ -70,23 +71,40 @@ def _ensure_thread(self) -> bool:
7071 return True
7172
7273 def _flush_loop (self ) -> None :
74+ # Mark the flush-loop thread as active for its entire lifetime so
75+ # that any re-entrant add() triggered by GC warnings during wait(),
76+ # flush(), or Event operations is silently dropped instead of
77+ # deadlocking on internal locks.
78+ self ._active .flag = True
7379 while self ._running :
7480 self ._flush_event .wait (self .FLUSH_WAIT_TIME + random .random ())
7581 self ._flush_event .clear ()
7682 self ._flush ()
7783
7884 def add (self , item : "T" ) -> None :
79- if not self ._ensure_thread () or self ._flusher is None :
85+ # Bail out if the current thread is already executing batcher code.
86+ # This prevents deadlocks when code running inside the batcher (e.g.
87+ # _add_to_envelope during flush, or _flush_event.wait/set) triggers
88+ # a GC-emitted warning that routes back through the logging
89+ # integration into add().
90+ if getattr (self ._active , "flag" , False ):
8091 return None
8192
82- with self ._lock :
83- if len ( self . _buffer ) >= self . MAX_BEFORE_DROP :
84- self ._record_lost ( item )
93+ self ._active . flag = True
94+ try :
95+ if not self ._ensure_thread () or self . _flusher is None :
8596 return None
8697
87- self ._buffer .append (item )
88- if len (self ._buffer ) >= self .MAX_BEFORE_FLUSH :
89- self ._flush_event .set ()
98+ with self ._lock :
99+ if len (self ._buffer ) >= self .MAX_BEFORE_DROP :
100+ self ._record_lost (item )
101+ return None
102+
103+ self ._buffer .append (item )
104+ if len (self ._buffer ) >= self .MAX_BEFORE_FLUSH :
105+ self ._flush_event .set ()
106+ finally :
107+ self ._active .flag = False
90108
91109 def kill (self ) -> None :
92110 if self ._flusher is None :
@@ -97,7 +115,11 @@ def kill(self) -> None:
97115 self ._flusher = None
98116
99117 def flush (self ) -> None :
100- self ._flush ()
118+ self ._active .flag = True
119+ try :
120+ self ._flush ()
121+ finally :
122+ self ._active .flag = False
101123
102124 def _add_to_envelope (self , envelope : "Envelope" ) -> None :
103125 envelope .add_item (
0 commit comments