Skip to content

Commit 2cad649

Browse files
committed
refactor: extract bounded TC-deferral fire-time into a cdef helper
1 parent e0eb9e0 commit 2cad649

3 files changed

Lines changed: 25 additions & 15 deletions

File tree

src/zeroconf/_listener.pxd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ cdef class AsyncListener:
3939

4040
cdef _cancel_any_timers_for_addr(self, object addr)
4141

42-
@cython.locals(incoming=DNSIncoming, deferred=list, now=double, delay=double, deadline=object, fire_at=double)
42+
@cython.locals(deadline=object, fire_at=double)
43+
cdef double _compute_deferred_fire_at(self, object addr, double now, double delay)
44+
45+
@cython.locals(incoming=DNSIncoming, deferred=list, now=double, delay=double, fire_at=double)
4346
cpdef handle_query_or_defer(
4447
self,
4548
DNSIncoming msg,

src/zeroconf/_listener.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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:

tests/test_core.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import zeroconf as r
2222
from zeroconf import NotRunningException, Zeroconf, const, current_time_millis
23-
from zeroconf._listener import AsyncListener, _WrappedTransport
23+
from zeroconf._listener import _TC_DELAY_RANDOM_INTERVAL, AsyncListener, _WrappedTransport
2424
from zeroconf._protocol.incoming import DNSIncoming
2525
from zeroconf.asyncio import AsyncZeroconf
2626

@@ -776,7 +776,8 @@ def test_tc_bit_defer_window_is_bounded():
776776

777777
# Pin the per-packet delay at its maximum so any subsequent reset would
778778
# land past the deadline established by the first packet.
779-
with patch("zeroconf._listener.random.randint", return_value=500):
779+
max_delay_ms = _TC_DELAY_RANDOM_INTERVAL[1]
780+
with patch("zeroconf._listener.random.randint", return_value=max_delay_ms):
780781
threadsafe_query(zc, protocol, r.DNSIncoming(packets[0]), source_ip, const._MDNS_PORT, Mock(), ())
781782
first_when = protocol._timers[source_ip].when()
782783

0 commit comments

Comments
 (0)