feat: improve performance of ServiceBrowser outgoing query scheduler … · python-zeroconf/python-zeroconf@963d022

@@ -996,6 +996,9 @@ def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT, v6_flow_scope=()):

996996

# Increase simulated time shift by 1/4 of the TTL in seconds

997997

time_offset += expected_ttl / 4

998998

now = _new_current_time_millis()

999+

# Force the next query to be sent since we are testing

1000+

# to see if the query contains answers and not the scheduler

1001+

browser.query_scheduler._next_time[type_] = now + (1000 * expected_ttl)

9991002

browser.reschedule_type(type_, now, now)

10001003

sleep_count += 1

10011004

await asyncio.wait_for(got_query.wait(), 1)

@@ -1244,3 +1247,67 @@ def update_service(self, zc, type_, name) -> None: # type: ignore[no-untyped-de

12441247

('add', '_http._tcp.local.', 'ShellyPro4PM-94B97EC07650._http._tcp.local.'),

12451248

('update', '_http._tcp.local.', 'ShellyPro4PM-94B97EC07650._http._tcp.local.'),

12461249

]

1250+1251+1252+

@pytest.mark.asyncio

1253+

async def test_service_browser_does_not_try_to_send_if_not_ready():

1254+

"""Test that the service browser does not try to send if not ready when rescheduling a type."""

1255+

service_added = asyncio.Event()

1256+

type_ = "_http._tcp.local."

1257+

registration_name = "nosend.%s" % type_

1258+1259+

def on_service_state_change(zeroconf, service_type, state_change, name):

1260+

if name == registration_name:

1261+

if state_change is ServiceStateChange.Added:

1262+

service_added.set()

1263+1264+

aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])

1265+

zeroconf_browser = aiozc.zeroconf

1266+

await zeroconf_browser.async_wait_for_start()

1267+1268+

expected_ttl = const._DNS_HOST_TTL

1269+

time_offset = 0.0

1270+1271+

def _new_current_time_millis():

1272+

"""Current system time in milliseconds"""

1273+

return (time.monotonic() * 1000) + (time_offset * 1000)

1274+1275+

assert len(zeroconf_browser.engine.protocols) == 2

1276+1277+

aio_zeroconf_registrar = AsyncZeroconf(interfaces=['127.0.0.1'])

1278+

zeroconf_registrar = aio_zeroconf_registrar.zeroconf

1279+

await aio_zeroconf_registrar.zeroconf.async_wait_for_start()

1280+

assert len(zeroconf_registrar.engine.protocols) == 2

1281+

with patch("zeroconf._services.browser.current_time_millis", _new_current_time_millis):

1282+

service_added = asyncio.Event()

1283+

browser = AsyncServiceBrowser(zeroconf_browser, type_, [on_service_state_change])

1284+

desc = {'path': '/~paulsm/'}

1285+

info = ServiceInfo(

1286+

type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")]

1287+

)

1288+

task = await aio_zeroconf_registrar.async_register_service(info)

1289+

await task

1290+1291+

try:

1292+

await asyncio.wait_for(service_added.wait(), 1)

1293+

time_offset = 1000 * expected_ttl # set the time to the end of the ttl

1294+

now = _new_current_time_millis()

1295+

browser.query_scheduler._next_time[type_] = now + (1000 * expected_ttl)

1296+

# Make sure the query schedule is to a time in the future

1297+

# so we will reschedule

1298+

with patch.object(

1299+

browser, "_async_send_ready_queries"

1300+

) as _async_send_ready_queries, patch.object(

1301+

browser, "_async_send_ready_queries_schedule_next"

1302+

) as _async_send_ready_queries_schedule_next:

1303+

# Reschedule the type to be sent in 1ms in the future

1304+

# to make sure the query is not sent

1305+

browser.reschedule_type(type_, now, now + 1)

1306+

assert not _async_send_ready_queries.called

1307+

await asyncio.sleep(0.01)

1308+

# Make sure it does happen after the sleep

1309+

assert _async_send_ready_queries_schedule_next.called

1310+

finally:

1311+

await aio_zeroconf_registrar.async_close()

1312+

await browser.async_cancel()

1313+

await aiozc.async_close()