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
997997time_offset += expected_ttl / 4
998998now = _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)
9991002browser.reschedule_type(type_, now, now)
10001003sleep_count += 1
10011004await 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()