Skip to content

Commit 2d1b832

Browse files
authored
Add coverage for sending answers removes future queued answers (#961)
- If we send an answer that is queued to be sent out in the future we should remove it from the queue as the question has already been answered and we do not want to generate additional traffic.
1 parent 7b125a1 commit 2d1b832

3 files changed

Lines changed: 52 additions & 9 deletions

File tree

tests/services/test_info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,7 @@ async def test_we_try_four_times_with_random_delay():
764764

765765
# we are going to patch the zeroconf send to check query transmission
766766
request_count = 0
767+
767768
def async_send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT):
768769
"""Sends an outgoing packet."""
769770
nonlocal request_count

tests/test_handlers.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,3 +1460,42 @@ async def test_response_aggregation_random_delay():
14601460
assert info3.dns_pointer() not in outgoing_queue.queue[1].answers
14611461
assert info4.dns_pointer() not in outgoing_queue.queue[1].answers
14621462
assert info5.dns_pointer() in outgoing_queue.queue[1].answers
1463+
1464+
1465+
@pytest.mark.asyncio
1466+
async def test_future_answers_are_removed_on_send():
1467+
"""Verify any future answers scheduled to be sent are removed when we send."""
1468+
type_ = "_mservice._tcp.local."
1469+
name = "xxxyyy"
1470+
registration_name = f"{name}.{type_}"
1471+
1472+
desc = {'path': '/~paulsm/'}
1473+
info = ServiceInfo(
1474+
type_, registration_name, 80, 0, 0, desc, "ash-1.local.", addresses=[socket.inet_aton("10.0.1.2")]
1475+
)
1476+
mocked_zc = unittest.mock.MagicMock()
1477+
outgoing_queue = MulticastOutgoingQueue(mocked_zc, 0, 0)
1478+
1479+
now = current_time_millis()
1480+
with unittest.mock.patch.object(_handlers, "_MULTICAST_DELAY_RANDOM_INTERVAL", (10, 10)):
1481+
outgoing_queue.async_add(now, {info.dns_pointer(): set()})
1482+
1483+
assert len(outgoing_queue.queue) == 1
1484+
1485+
with unittest.mock.patch.object(_handlers, "_MULTICAST_DELAY_RANDOM_INTERVAL", (20, 20)):
1486+
outgoing_queue.async_add(now, {info.dns_pointer(): set()})
1487+
1488+
assert len(outgoing_queue.queue) == 2
1489+
1490+
with unittest.mock.patch.object(_handlers, "_MULTICAST_DELAY_RANDOM_INTERVAL", (200, 200)):
1491+
outgoing_queue.async_add(now, {info.dns_pointer(): set()})
1492+
outgoing_queue.async_add(now, {info.dns_pointer(): set()})
1493+
1494+
assert len(outgoing_queue.queue) == 3
1495+
1496+
await asyncio.sleep(0.1)
1497+
outgoing_queue.async_ready()
1498+
1499+
assert len(outgoing_queue.queue) == 1
1500+
# The answers should all get removed because we just sent them
1501+
assert len(outgoing_queue.queue[0].answers) == 0

zeroconf/_handlers.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -527,18 +527,24 @@ def async_add(self, now: float, answers: _AnswerWithAdditionalsType) -> None:
527527
last_group.answers.update(answers)
528528
return
529529
else:
530-
self.zc.loop.call_later(millis_to_seconds(random_delay), self._async_ready)
530+
self.zc.loop.call_later(millis_to_seconds(random_delay), self.async_ready)
531531
self.queue.append(AnswerGroup(send_after, send_before, answers))
532532

533-
def _async_ready(self) -> None:
533+
def _remove_answers_from_queue(self, answers: _AnswerWithAdditionalsType) -> None:
534+
"""Remove a set of answers from the outgoing queue."""
535+
for pending in self.queue:
536+
for record in answers:
537+
pending.answers.pop(record, None)
538+
539+
def async_ready(self) -> None:
534540
"""Process anything in the queue that is ready."""
535541
assert self.zc.loop is not None
536542
now = current_time_millis()
537543

538544
if len(self.queue) > 1 and self.queue[0].send_before > now:
539545
# There is more than one answer in the queue,
540546
# delay until we have to send it (first answer group reaches send_before)
541-
self.zc.loop.call_later(millis_to_seconds(self.queue[0].send_before - now), self._async_ready)
547+
self.zc.loop.call_later(millis_to_seconds(self.queue[0].send_before - now), self.async_ready)
542548
return
543549

544550
answers: _AnswerWithAdditionalsType = {}
@@ -549,12 +555,9 @@ def _async_ready(self) -> None:
549555
if len(self.queue):
550556
# If there are still groups in the queue that are not ready to send
551557
# be sure we schedule them to go out later
552-
self.zc.loop.call_later(millis_to_seconds(self.queue[0].send_after - now), self._async_ready)
558+
self.zc.loop.call_later(millis_to_seconds(self.queue[0].send_after - now), self.async_ready)
553559

554560
if answers:
555-
# If we have the same answer scheduled to go out, remove it
556-
for pending in self.queue:
557-
for record in answers:
558-
pending.answers.pop(record, None)
559-
561+
# If we have the same answer scheduled to go out, remove them
562+
self._remove_answers_from_queue(answers)
560563
self.zc.async_send(construct_outgoing_multicast_answers(answers))

0 commit comments

Comments
 (0)