Skip to content

No way to use async service listeners #1286

@tomkcook

Description

@tomkcook

The problem is demonstrated with this code:

import asyncio
from zeroconf import Zeroconf, ServiceStateChange
from zeroconf.asyncio import AsyncServiceBrowser

def on_service_state_change(zeroconf, service_type, name, state_change):
    if state_change == ServiceStateChange.Added:
        info = zeroconf.get_service_info(service_type, name)
        print(info)

async def browse_services():
    zeroconf = Zeroconf()
    service_type = "_http._tcp.local."  # Replace with the service type you want to browse

    browser = AsyncServiceBrowser(zeroconf, service_type, handlers=[on_service_state_change])

    try:
        await asyncio.Event().wait()
    finally:
        zeroconf.close()

if __name__ == "__main__":
    asyncio.run(browse_services())

This produces this output:

Exception in callback _SelectorDatagramTransport._read_ready()
handle: <Handle _SelectorDatagramTransport._read_ready()>
Traceback (most recent call last):
  File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/lib/python3.10/asyncio/selector_events.py", line 1035, in _read_ready
    self._protocol.datagram_received(data, addr)
  File "src/zeroconf/_listener.py", line 80, in zeroconf._listener.AsyncListener.datagram_received
  File "src/zeroconf/_listener.py", line 161, in zeroconf._listener.AsyncListener.datagram_received
  File "src/zeroconf/_handlers/record_manager.py", line 162, in zeroconf._handlers.record_manager.RecordManager.async_updates_from_response
  File "src/zeroconf/_handlers/record_manager.py", line 71, in zeroconf._handlers.record_manager.RecordManager.async_updates_complete
  File "src/zeroconf/_services/browser.py", line 441, in zeroconf._services.browser._ServiceBrowserBase.async_update_records_complete
  File "src/zeroconf/_services/browser.py", line 451, in zeroconf._services.browser._ServiceBrowserBase.async_update_records_complete
  File "src/zeroconf/_services/browser.py", line 454, in zeroconf._services.browser._ServiceBrowserBase._fire_service_state_changed_event
  File "src/zeroconf/_services/browser.py", line 454, in zeroconf._services.browser._ServiceBrowserBase._fire_service_state_changed_event
  File "src/zeroconf/_services/browser.py", line 464, in zeroconf._services.browser._ServiceBrowserBase._fire_service_state_changed_event
  File "/home/tkcook/.virtualenvs/veeahub/lib/python3.10/site-packages/zeroconf/_services/__init__.py", line 56, in fire
    h(**kwargs)
  File "/home/tkcook/Scratch/zeroconf_test.py", line 7, in on_service_state_change
    info = zeroconf.get_service_info(service_type, name)
  File "/home/tkcook/.virtualenvs/veeahub/lib/python3.10/site-packages/zeroconf/_core.py", line 270, in get_service_info
    if info.request(self, timeout, question_type):
  File "src/zeroconf/_services/info.py", line 752, in zeroconf._services.info.ServiceInfo.request
RuntimeError: Use AsyncServiceInfo.async_request from the event loop

But it is not possible to use async_request (and by implication async_get_service_info) because the handler is not async. There doesn't seem to be any way to register an async handler. I've tried this:

import asyncio
from zeroconf import Zeroconf, ServiceStateChange
from zeroconf.asyncio import AsyncServiceBrowser

class Foo:
    async def add_service(self, *args, **kwargs):
        print("add_service")

    async def remove_service(self, *args, **kwargs):
        print("remove_service")

    async def update_service(self, *args, **kwargs):
        print("update_service")

async def browse_services():
    zeroconf = Zeroconf()
    service_type = "_http._tcp.local."  # Replace with the service type you want to browse

    browser = AsyncServiceBrowser(zeroconf, service_type, listener=Foo())

    try:
        await asyncio.Event().wait()
    finally:
        zeroconf.close()

if __name__ == "__main__":
    asyncio.run(browse_services())

but this still results in:

/home/tkcook/.virtualenvs/veeahub/lib/python3.10/site-packages/zeroconf/_services/__init__.py:56: RuntimeWarning: coroutine 'Foo.add_service' was never awaited
  h(**kwargs)

So there doesn't seem to be any way to use zeroconf from within a service listener.

Am I missing something? Is this a defect?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions