← 输入框|github.com|刷新
Skip to content

No way to use async service listeners #1286

Closed

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?

Activity

  1. tomkcook commented on Oct 19, 2023

    @tomkcook
    Author

    For anyone else who comes along, this works:

    def on_service_state_change(zeroconf, service_type, name, state_change):
        async def handler():
            if state_change == ServiceStateChange.Added:
                info = zeroconf.get_service_info(service_type, name)
                print(info)
        asyncio.ensure_future(handler())
    
  2. bdraco commented on Oct 30, 2023

    @bdraco
    Member

    def get_service_info(

    get_service_info is the sync version.

    use AsyncServiceInfo instead

    info = AsyncServiceInfo(type_, name)
    await info.async_request(zeroconf, _TIMEOUT_MS)
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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