Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 963d022

Browse filesBrowse files
authored
feat: improve performance of ServiceBrowser outgoing query scheduler (#1170)
1 parent 90410a2 commit 963d022
Copy full SHA for 963d022

File tree

2 files changed

+73
-1
lines changed
Filter options

2 files changed

+73
-1
lines changed

‎src/zeroconf/_services/browser.py

Copy file name to clipboardExpand all lines: src/zeroconf/_services/browser.py
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,13 +460,18 @@ def _cancel_send_timer(self) -> None:
460460
"""Cancel the next send."""
461461
if self._next_send_timer:
462462
self._next_send_timer.cancel()
463+
self._next_send_timer = None
463464

464465
def reschedule_type(self, type_: str, now: float, next_time: float) -> None:
465466
"""Reschedule a type to be refreshed in the future."""
466467
if self.query_scheduler.reschedule_type(type_, next_time):
468+
# We need to send the queries before rescheduling the next one
469+
# otherwise we may be scheduling a query to go out in the next
470+
# iteration of the event loop which should be sent now.
471+
if now >= next_time:
472+
self._async_send_ready_queries(now)
467473
self._cancel_send_timer()
468474
self._async_schedule_next(now)
469-
self._async_send_ready_queries(now)
470475

471476
def _async_send_ready_queries(self, now: float) -> None:
472477
"""Send any ready queries."""

‎tests/test_asyncio.py

Copy file name to clipboardExpand all lines: tests/test_asyncio.py
+67Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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()

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.