2525import functools
2626import itertools
2727import logging
28- import sys
2928import threading
3029import time
3130import types
7372 bool ,
7473 type (None ),
7574 range ,
76- slice ,
7775 type ,
7876 types .ModuleType ,
7977 types .CodeType ,
@@ -135,6 +133,8 @@ def _retained_children(value: Any) -> Any:
135133 return itertools .chain (frozenset .__iter__ (value ), custom )
136134 if isinstance (value , deque ):
137135 return itertools .chain (deque .__iter__ (value ), custom )
136+ if isinstance (value , slice ):
137+ return itertools .chain ((value .start , value .stop , value .step ), custom )
138138 if isinstance (value , memoryview ):
139139 return itertools .chain ((value .obj ,), custom )
140140 if isinstance (value , functools .partial ):
@@ -175,26 +175,25 @@ def _retained_children(value: Any) -> Any:
175175
176176
177177def _retained_shallow_size (value : Any ) -> int :
178- try :
179- size = sys .getsizeof (value )
180- except Exception : # noqa: BLE001 - estimation must never break a hook
181- size = 1_024
178+ """Return shallow size without dispatching to user-defined ``__sizeof__``."""
182179 try :
183180 if isinstance (value , dict ):
184- size = max (size , dict .__sizeof__ (value ))
185- elif isinstance (value , list ):
186- size = max (size , list .__sizeof__ (value ))
187- elif isinstance (value , tuple ):
188- size = max (size , tuple .__sizeof__ (value ))
189- elif isinstance (value , set ):
190- size = max (size , set .__sizeof__ (value ))
191- elif isinstance (value , frozenset ):
192- size = max (size , frozenset .__sizeof__ (value ))
193- elif isinstance (value , deque ):
194- size = max (size , deque .__sizeof__ (value ))
195- except Exception : # noqa: BLE001 - base sizing remains best-effort
196- pass
197- return size
181+ return dict .__sizeof__ (value )
182+ if isinstance (value , list ):
183+ return list .__sizeof__ (value )
184+ if isinstance (value , tuple ):
185+ return tuple .__sizeof__ (value )
186+ if isinstance (value , set ):
187+ return set .__sizeof__ (value )
188+ if isinstance (value , frozenset ):
189+ return frozenset .__sizeof__ (value )
190+ if isinstance (value , deque ):
191+ return deque .__sizeof__ (value )
192+ if type (value ) in _ATOMIC_RETAINED_TYPES + _SAFE_OPAQUE_RETAINED_TYPES :
193+ return value .__sizeof__ ()
194+ return object .__sizeof__ (value )
195+ except Exception : # noqa: BLE001 - estimation must never break a hook
196+ return 1_024
198197
199198
200199def _estimate_retained_size (value : Any , max_size : int | None = None ) -> int :
@@ -313,6 +312,7 @@ def __init__(
313312 self ._max_pending_per_execution = max (1 , max_pending_records_per_execution )
314313 self ._max_pending_bytes = max (1 , max_pending_bytes )
315314 self ._pending_bytes = 0
315+ self ._inflight_bytes = 0
316316 # Explicit non-reentrant Lock rather than Condition()'s default RLock:
317317 # the lane never re-acquires ``_cond`` while already holding it (worker
318318 # I/O -- export/flush -- runs outside the lock and no locked helper
@@ -412,6 +412,12 @@ def cancel_flush(self, barrier: _FlushBarrier) -> None:
412412 """Stop waiting for a timed-out barrier while retaining one later flush."""
413413 with self ._cond :
414414 barrier .canceled = True
415+ if not any (
416+ kind == _FLUSH and payload is barrier for kind , payload in self ._queue
417+ ):
418+ # The worker already owns this barrier. Do not erase a detached
419+ # flush installed by a later invocation while this one was in flight.
420+ return
415421 # Keep at most one detached flush. Moving it to this barrier's
416422 # position makes it cover all work scheduled before the latest
417423 # timeout without accumulating one marker per warm invocation.
@@ -481,7 +487,10 @@ def _enforce_pending_record_cap(self) -> None:
481487 )
482488
483489 def _enforce_pending_byte_cap (self ) -> None :
484- while self ._pending_bytes > self ._max_pending_bytes and self ._pending :
490+ while (
491+ self ._pending_bytes + self ._inflight_bytes > self ._max_pending_bytes
492+ and self ._pending
493+ ):
485494 old_arn = self ._oldest_pending_arn ()
486495 dropped_size = self ._drop_oldest_pending_record (old_arn )
487496 _logger .warning (
@@ -523,6 +532,7 @@ def _disable_locked(self, exc: Exception) -> None:
523532 self ._worker = None
524533 self ._pending .clear ()
525534 self ._pending_bytes = 0
535+ self ._inflight_bytes = 0
526536 for kind , payload in self ._queue :
527537 if kind == _FLUSH and payload is not None :
528538 barrier : _FlushBarrier = payload
@@ -571,14 +581,17 @@ def _run_worker(self) -> None:
571581 return
572582 kind , payload = self ._queue .popleft ()
573583 record : dict [str , Any ] | None = None
584+ record_size = 0
574585 if kind == _RECORD :
575586 token : _RecordToken = payload
576587 execution_arn , generation = token
577588 pending = self ._pending .get (execution_arn )
578589 if not pending or pending [0 ].generation != generation :
579590 continue
580591 pending_record = pending .popleft ()
581- self ._pending_bytes -= pending_record .size
592+ record_size = pending_record .size
593+ self ._pending_bytes -= record_size
594+ self ._inflight_bytes += record_size
582595 record = pending_record .value
583596 if pending and pending [0 ].generation == generation :
584597 # One record per ARN turn within this barrier generation.
@@ -587,7 +600,11 @@ def _run_worker(self) -> None:
587600 del self ._pending [execution_arn ]
588601
589602 if kind == _RECORD and record is not None :
590- self ._export_one (record )
603+ try :
604+ self ._export_one (record )
605+ finally :
606+ with self ._cond :
607+ self ._inflight_bytes -= record_size
591608 else : # _FLUSH
592609 barrier : _FlushBarrier | None = payload
593610 self ._flush ()
@@ -658,6 +675,10 @@ def _pending_bytes_count(self) -> int:
658675 with self ._cond :
659676 return self ._pending_bytes
660677
678+ def _retained_bytes_count (self ) -> int :
679+ with self ._cond :
680+ return self ._pending_bytes + self ._inflight_bytes
681+
661682 def _queue_len (self ) -> int :
662683 with self ._cond :
663684 return len (self ._queue )
0 commit comments