Skip to content

Navigation Menu

Sign in
Appearance settings

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

feat: improve performance of ServiceBrowser outgoing query scheduler #1170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion 7 src/zeroconf/_services/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,13 +460,18 @@ def _cancel_send_timer(self) -> None:
"""Cancel the next send."""
if self._next_send_timer:
self._next_send_timer.cancel()
self._next_send_timer = None

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

def _async_send_ready_queries(self, now: float) -> None:
"""Send any ready queries."""
Expand Down
67 changes: 67 additions & 0 deletions 67 tests/test_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,9 @@ def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT, v6_flow_scope=()):
# Increase simulated time shift by 1/4 of the TTL in seconds
time_offset += expected_ttl / 4
now = _new_current_time_millis()
# Force the next query to be sent since we are testing
# to see if the query contains answers and not the scheduler
browser.query_scheduler._next_time[type_] = now + (1000 * expected_ttl)
browser.reschedule_type(type_, now, now)
sleep_count += 1
await asyncio.wait_for(got_query.wait(), 1)
Expand Down Expand Up @@ -1244,3 +1247,67 @@ def update_service(self, zc, type_, name) -> None: # type: ignore[no-untyped-de
('add', '_http._tcp.local.', 'ShellyPro4PM-94B97EC07650._http._tcp.local.'),
('update', '_http._tcp.local.', 'ShellyPro4PM-94B97EC07650._http._tcp.local.'),
]


@pytest.mark.asyncio
async def test_service_browser_does_not_try_to_send_if_not_ready():
"""Test that the service browser does not try to send if not ready when rescheduling a type."""
service_added = asyncio.Event()
type_ = "_http._tcp.local."
registration_name = "nosend.%s" % type_

def on_service_state_change(zeroconf, service_type, state_change, name):
if name == registration_name:
if state_change is ServiceStateChange.Added:
service_added.set()

aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])
zeroconf_browser = aiozc.zeroconf
await zeroconf_browser.async_wait_for_start()

expected_ttl = const._DNS_HOST_TTL
time_offset = 0.0

def _new_current_time_millis():
"""Current system time in milliseconds"""
return (time.monotonic() * 1000) + (time_offset * 1000)

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

aio_zeroconf_registrar = AsyncZeroconf(interfaces=['127.0.0.1'])
zeroconf_registrar = aio_zeroconf_registrar.zeroconf
await aio_zeroconf_registrar.zeroconf.async_wait_for_start()
assert len(zeroconf_registrar.engine.protocols) == 2
with patch("zeroconf._services.browser.current_time_millis", _new_current_time_millis):
service_added = asyncio.Event()
browser = AsyncServiceBrowser(zeroconf_browser, type_, [on_service_state_change])
desc = {'path': '/~paulsm/'}
info = ServiceInfo(
type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")]
)
task = await aio_zeroconf_registrar.async_register_service(info)
await task

try:
await asyncio.wait_for(service_added.wait(), 1)
time_offset = 1000 * expected_ttl # set the time to the end of the ttl
now = _new_current_time_millis()
browser.query_scheduler._next_time[type_] = now + (1000 * expected_ttl)
# Make sure the query schedule is to a time in the future
# so we will reschedule
with patch.object(
browser, "_async_send_ready_queries"
) as _async_send_ready_queries, patch.object(
browser, "_async_send_ready_queries_schedule_next"
) as _async_send_ready_queries_schedule_next:
# Reschedule the type to be sent in 1ms in the future
# to make sure the query is not sent
browser.reschedule_type(type_, now, now + 1)
assert not _async_send_ready_queries.called
bdraco marked this conversation as resolved.
Show resolved Hide resolved
await asyncio.sleep(0.01)
# Make sure it does happen after the sleep
assert _async_send_ready_queries_schedule_next.called
finally:
await aio_zeroconf_registrar.async_close()
await browser.async_cancel()
await aiozc.async_close()
Morty Proxy This is a proxified and sanitized view of the page, visit original site.