Skip to content

Commit c772936

Browse files
authored
Reduce chance of accidental synchronization of ServiceInfo requests (#955)
1 parent 5fb3e20 commit c772936

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

tests/services/test_info.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,3 +754,25 @@ def test_request_timeout():
754754
# 3000ms for the default timeout
755755
# 1000ms for loaded systems + schedule overhead
756756
assert (end_time - start_time) < 3000 + 1000
757+
758+
759+
@pytest.mark.asyncio
760+
async def test_we_try_four_times_with_random_delay():
761+
"""Verify we try four times even with the random delay."""
762+
type_ = "_typethatisnothere._tcp.local."
763+
aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])
764+
765+
# we are going to patch the zeroconf send to check query transmission
766+
request_count = 0
767+
def async_send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT):
768+
"""Sends an outgoing packet."""
769+
nonlocal request_count
770+
request_count += 1
771+
772+
# patch the zeroconf send
773+
with patch.object(aiozc.zeroconf, "async_send", async_send):
774+
await aiozc.async_get_service_info(f"willnotbefound.{type_}", type_)
775+
776+
await aiozc.async_close()
777+
778+
assert request_count == 4

zeroconf/_services/info.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"""
2222

2323
import ipaddress
24+
import random
2425
import socket
2526
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union, cast
2627

@@ -52,6 +53,16 @@
5253
)
5354

5455

56+
# https://datatracker.ietf.org/doc/html/rfc6762#section-5.2
57+
# The most common case for calling ServiceInfo is from a
58+
# ServiceBrowser. After the first request we add a few random
59+
# milliseconds to the delay between requests to reduce the chance
60+
# that there are multiple ServiceBrowser callbacks running on
61+
# the network that are firing at the same time when they
62+
# see the same multicast response and decide to refresh
63+
# the A/AAAA/SRV records for a host.
64+
_AVOID_SYNC_DELAY_RANDOM_INTERVAL = (20, 120)
65+
5566
if TYPE_CHECKING:
5667
from .._core import Zeroconf
5768

@@ -455,6 +466,7 @@ async def async_request(
455466
zc.async_send(out)
456467
next_ = now + delay
457468
delay *= 2
469+
next_ += random.randint(*_AVOID_SYNC_DELAY_RANDOM_INTERVAL)
458470

459471
await zc.async_wait(min(next_, last) - now)
460472
now = current_time_millis()

0 commit comments

Comments
 (0)