@@ -209,18 +209,9 @@ def handle_query_or_defer(
209209 assert loop is not None
210210 now = loop .time ()
211211 delay = millis_to_seconds (random .randint (* _TC_DELAY_RANDOM_INTERVAL )) # noqa: S311
212- # Bound the assembly window to first_arrival + max delay so a peer
213- # streaming TC packets cannot keep deferring the flush indefinitely.
214- deadline = self ._deferred_deadlines .get (addr )
215- if deadline is None :
216- deadline = now + millis_to_seconds (_TC_DELAY_RANDOM_INTERVAL [1 ])
217- self ._deferred_deadlines [addr ] = deadline
218- fire_at = now + delay
219- if fire_at >= deadline :
220- # Existing timer (if any) already fires at or before the deadline.
221- if addr in self ._timers :
222- return
223- fire_at = deadline
212+ fire_at = self ._compute_deferred_fire_at (addr , now , delay )
213+ if fire_at < 0.0 :
214+ return
224215 self ._cancel_any_timers_for_addr (addr )
225216 self ._timers [addr ] = loop .call_at (
226217 fire_at ,
@@ -232,6 +223,21 @@ def handle_query_or_defer(
232223 v6_flow_scope ,
233224 )
234225
226+ def _compute_deferred_fire_at (self , addr : _str , now : _float , delay : _float ) -> _float :
227+ """Return the bounded call_at time for a TC-deferred flush, or -1.0 to keep the existing timer."""
228+ # RFC 6762 §18.5 frames the random delay as a fixed reassembly budget
229+ # starting at first arrival, not a sliding heartbeat.
230+ deadline = self ._deferred_deadlines .get (addr )
231+ if deadline is None :
232+ deadline = now + millis_to_seconds (_TC_DELAY_RANDOM_INTERVAL [1 ])
233+ self ._deferred_deadlines [addr ] = deadline
234+ fire_at = now + delay
235+ if fire_at >= deadline :
236+ if addr in self ._timers :
237+ return - 1.0
238+ return deadline
239+ return fire_at
240+
235241 def _cancel_any_timers_for_addr (self , addr : _str ) -> None :
236242 """Cancel any future truncated packet timers for the address."""
237243 if addr in self ._timers :
0 commit comments