2020 USA
2121"""
2222
23+ import asyncio
24+ import contextlib
25+ import queue
2326import threading
2427import warnings
2528from collections import OrderedDict
3538 Signal ,
3639 SignalRegistrationInterface ,
3740)
41+ from .._utils .aio import get_best_available_queue , get_running_loop , wait_condition_or_timeout
3842from .._utils .name import service_type_name
3943from .._utils .time import current_time_millis , millis_to_seconds
4044from ..const import (
@@ -180,6 +184,7 @@ def __init__(
180184 for check_type_ in self .types :
181185 # Will generate BadTypeInNameException on a bad name
182186 service_type_name (check_type_ , strict = False )
187+ self ._browser_task : Optional [asyncio .Task ] = None
183188 self .zc = zc
184189 self .addr = addr
185190 self .port = port
@@ -190,7 +195,7 @@ def __init__(
190195 self ._pending_handlers : OrderedDict [Tuple [str , str ], ServiceStateChange ] = OrderedDict ()
191196 self ._handlers_to_call : OrderedDict [Tuple [str , str ], ServiceStateChange ] = OrderedDict ()
192197 self ._service_state_changed = Signal ()
193-
198+ self . queue : Optional [ queue . Queue ] = None
194199 self .done = False
195200
196201 if hasattr (handlers , 'add_service' ):
@@ -341,6 +346,47 @@ def _seconds_to_wait(self) -> Optional[float]:
341346
342347 return millis_to_seconds (next_time - now )
343348
349+ async def async_browser_task (self ) -> None :
350+ """Run the browser task."""
351+ await self .zc .async_wait_for_start ()
352+ assert self .zc .async_condition is not None
353+ while True :
354+ timeout = self ._seconds_to_wait ()
355+ if timeout :
356+ async with self .zc .async_condition :
357+ # We must check again while holding the condition
358+ # in case the other thread has added to _handlers_to_call
359+ # between when we checked above when we were not
360+ # holding the condition
361+ if not self ._handlers_to_call :
362+ await wait_condition_or_timeout (self .zc .async_condition , timeout )
363+
364+ outs = self .generate_ready_queries ()
365+ for out in outs :
366+ self .zc .async_send (out , addr = self .addr , port = self .port )
367+
368+ if not self ._handlers_to_call :
369+ continue
370+
371+ (name_type , state_change ) = self ._handlers_to_call .popitem (False )
372+ if self .queue :
373+ self .queue .put ((name_type , state_change ))
374+ continue
375+
376+ self ._service_state_changed .fire (
377+ zeroconf = self .zc ,
378+ service_type = name_type [1 ],
379+ name = name_type [0 ],
380+ state_change = state_change ,
381+ )
382+
383+ async def _async_cancel_browser (self ) -> None :
384+ """Cancel the browser."""
385+ assert self ._browser_task is not None
386+ self ._browser_task .cancel ()
387+ with contextlib .suppress (asyncio .CancelledError ):
388+ await self ._browser_task
389+
344390
345391class ServiceBrowser (_ServiceBrowserBase , threading .Thread ):
346392 """Used to browse for a service of a specific type.
@@ -361,42 +407,45 @@ def __init__(
361407 ) -> None :
362408 threading .Thread .__init__ (self )
363409 super ().__init__ (zc , type_ , handlers = handlers , listener = listener , addr = addr , port = port , delay = delay )
410+ self .queue = get_best_available_queue ()
364411 self .daemon = True
365412 self .start ()
366413 self .name = "zeroconf-ServiceBrowser-%s-%s" % (
367414 '-' .join ([type_ [:- 7 ] for type_ in self .types ]),
368415 getattr (self , 'native_id' , self .ident ),
369416 )
417+ assert self .zc .loop is not None
418+ if get_running_loop () == self .zc .loop :
419+ self ._browser_task = cast (asyncio .Task , asyncio .ensure_future (self .async_browser_task ()))
420+ return
421+ self ._browser_task = cast (
422+ asyncio .Task ,
423+ asyncio .run_coroutine_threadsafe (self ._async_browser_task (), self .zc .loop ).result (),
424+ )
425+
426+ async def _async_browser_task (self ) -> asyncio .Task :
427+ return cast (asyncio .Task , asyncio .ensure_future (self .async_browser_task ()))
370428
371429 def cancel (self ) -> None :
372430 """Cancel the browser."""
431+ assert self .zc .loop is not None
432+ assert self .queue is not None
433+ self .queue .put (None )
434+ if get_running_loop () == self .zc .loop :
435+ asyncio .ensure_future (self ._async_cancel_browser ())
436+ else :
437+ asyncio .run_coroutine_threadsafe (self ._async_cancel_browser (), self .zc .loop ).result ()
373438 super ().cancel ()
374439 self .join ()
375440
376441 def run (self ) -> None :
377442 """Run the browser thread."""
443+ assert self .queue is not None
378444 while True :
379- timeout = self ._seconds_to_wait ()
380- if timeout :
381- with self .zc .condition :
382- # We must check again while holding the condition
383- # in case the other thread has added to _handlers_to_call
384- # between when we checked above when we were not
385- # holding the condition
386- if not self ._handlers_to_call :
387- self .zc .condition .wait (timeout )
388-
389- if self .zc .done or self .done :
445+ event = self .queue .get ()
446+ if event is None :
390447 return
391-
392- outs = self .generate_ready_queries ()
393- for out in outs :
394- self .zc .send (out , addr = self .addr , port = self .port )
395-
396- if not self ._handlers_to_call :
397- continue
398-
399- (name_type , state_change ) = self ._handlers_to_call .popitem (False )
448+ name_type , state_change = event
400449 self ._service_state_changed .fire (
401450 zeroconf = self .zc ,
402451 service_type = name_type [1 ],
0 commit comments