2121"""
2222
2323import asyncio
24- import contextlib
2524import queue
2625import random
2726import threading
3938 SignalRegistrationInterface ,
4039)
4140from .._updates import RecordUpdate , RecordUpdateListener
42- from .._utils .aio import get_best_available_queue , wait_event_or_timeout
41+ from .._utils .aio import get_best_available_queue
4342from .._utils .name import service_type_name
4443from .._utils .time import current_time_millis , millis_to_seconds
4544from ..const import (
@@ -221,25 +220,17 @@ def _generate_first_next_time(self, now: float) -> None:
221220 next_time = now + delay
222221 self ._next_time = {check_type_ : next_time for check_type_ in self ._types }
223222
224- def millis_to_wait (self , now : float ) -> Optional [ float ] :
223+ def millis_to_wait (self , now : float ) -> float :
225224 """Returns the number of milliseconds to wait for the next event."""
226225 # Wait for the type has the smallest next time
227226 next_time = min (self ._next_time .values ())
228- return None if next_time <= now else next_time - now
227+ return 0 if next_time <= now else next_time - now
229228
230229 def reschedule_type (self , type_ : str , next_time : float ) -> None :
231230 """Reschedule the query for a type to happen sooner."""
232231 if next_time >= self ._next_time [type_ ]:
233232 return
234-
235233 self ._next_time [type_ ] = next_time
236- self .set_schedule_changed ()
237-
238- def set_schedule_changed (self ) -> None :
239- """Set the event to unblock async_wait_ready to make sure the adjusted next time is seen."""
240- assert self ._schedule_changed_event is not None
241- self ._schedule_changed_event .set ()
242- self ._schedule_changed_event .clear ()
243234
244235 def process_ready_types (self , now : float ) -> List [str ]:
245236 """Generate a list of ready types that is due and schedule the next time."""
@@ -258,13 +249,6 @@ def process_ready_types(self, now: float) -> List[str]:
258249
259250 return ready_types
260251
261- async def async_wait_ready (self , now : float ) -> None :
262- """Wait for at least one query to be ready."""
263- timeout = self .millis_to_wait (now )
264- if timeout :
265- assert self ._schedule_changed_event is not None
266- await wait_event_or_timeout (self ._schedule_changed_event , timeout = millis_to_seconds (timeout ))
267-
268252
269253class _ServiceBrowserBase (RecordUpdateListener ):
270254 """Base class for ServiceBrowser."""
@@ -302,7 +286,6 @@ def __init__(
302286 for check_type_ in self .types :
303287 # Will generate BadTypeInNameException on a bad name
304288 service_type_name (check_type_ , strict = False )
305- self ._browser_task : Optional [asyncio .Task ] = None
306289 self .zc = zc
307290 self .addr = addr
308291 self .port = port
@@ -313,6 +296,8 @@ def __init__(
313296 self .query_scheduler = QueryScheduler (self .types , delay , _FIRST_QUERY_DELAY_RANDOM_INTERVAL )
314297 self .queue : Optional [queue .Queue ] = None
315298 self .done = False
299+ self ._first_request : bool = True
300+ self ._next_send_timer : Optional [asyncio .TimerHandle ] = None
316301
317302 if hasattr (handlers , 'add_service' ):
318303 listener = cast ('ServiceListener' , handlers )
@@ -335,7 +320,7 @@ def _async_start(self) -> None:
335320 self .query_scheduler .start (current_time_millis ())
336321 self .zc .async_add_listener (self , [DNSQuestion (type_ , _TYPE_PTR , _CLASS_IN ) for type_ in self .types ])
337322 # Only start queries after the listener is installed
338- self . _browser_task = cast ( asyncio .Task , asyncio . ensure_future (self .async_browser_task () ))
323+ asyncio .ensure_future (self ._async_start_query_sender ( ))
339324
340325 @property
341326 def service_state_changed (self ) -> SignalRegistrationInterface :
@@ -378,9 +363,7 @@ def _async_process_record_update(
378363 elif expired :
379364 self ._enqueue_callback (ServiceStateChange .Removed , record .name , record .alias )
380365 else :
381- self .query_scheduler .reschedule_type (
382- record .name , record .get_expiration_time (_EXPIRE_REFRESH_TIME_PERCENT )
383- )
366+ self .reschedule_type (record .name , record .get_expiration_time (_EXPIRE_REFRESH_TIME_PERCENT ))
384367 return
385368
386369 # If its expired or already exists in the cache it cannot be updated.
@@ -448,6 +431,7 @@ def _fire_service_state_changed_event(self, event: Tuple[Tuple[str, str], Servic
448431 def _async_cancel (self ) -> None :
449432 """Cancel the browser."""
450433 self .done = True
434+ self ._cancel_send_timer ()
451435 self .zc .async_remove_listener (self )
452436
453437 def _generate_ready_queries (self , first_request : bool ) -> List [DNSOutgoing ]:
@@ -464,28 +448,40 @@ def _generate_ready_queries(self, first_request: bool) -> List[DNSOutgoing]:
464448 question_type = DNSQuestionType .QU if not self .question_type and first_request else self .question_type
465449 return generate_service_query (self .zc , now , ready_types , self .multicast , question_type )
466450
467- async def async_browser_task (self ) -> None :
468- """Run the browser task ."""
451+ async def _async_start_query_sender (self ) -> None :
452+ """Start scheduling queries ."""
469453 await self .zc .async_wait_for_start ()
470- first_request = True
471- while True :
472- await self . query_scheduler . async_wait_ready ( current_time_millis ())
473- outs = self . _generate_ready_queries ( first_request )
474- if not outs :
475- continue
454+ self . _async_send_ready_queries_schedule_next ()
455+
456+ def _cancel_send_timer ( self ) -> None :
457+ """Cancel the next send."""
458+ if self . _next_send_timer :
459+ self . _next_send_timer . cancel ()
476460
477- first_request = False
461+ def reschedule_type (self , type_ : str , next_time : float ) -> None :
462+ """Reschedule a type to be refreshed in the future."""
463+ self .query_scheduler .reschedule_type (type_ , next_time )
464+ self .schedule_changed ()
465+
466+ def schedule_changed (self ) -> None :
467+ """Called when the schedule has changed."""
468+ self ._cancel_send_timer ()
469+ self ._async_send_ready_queries_schedule_next ()
470+
471+ def _async_send_ready_queries_schedule_next (self ) -> None :
472+ """Send any ready queries and scheule the next time."""
473+ if self .done or self .zc .done :
474+ return
475+
476+ outs = self ._generate_ready_queries (self ._first_request )
477+ if outs :
478+ self ._first_request = False
478479 for out in outs :
479480 self .zc .async_send (out , addr = self .addr , port = self .port )
480481
481- async def _async_cancel_browser (self ) -> None :
482- """Cancel the browser."""
483- assert self ._browser_task is not None
484- self ._browser_task .cancel ()
485- browser_task = self ._browser_task
486- self ._browser_task = None
487- with contextlib .suppress (asyncio .CancelledError ):
488- await browser_task
482+ assert self .zc .loop is not None
483+ delay = millis_to_seconds (self .query_scheduler .millis_to_wait (current_time_millis ()))
484+ self ._next_send_timer = self .zc .loop .call_later (delay , self ._async_send_ready_queries_schedule_next )
489485
490486
491487class ServiceBrowser (_ServiceBrowserBase , threading .Thread ):
@@ -523,18 +519,12 @@ def __init__(
523519 getattr (self , 'native_id' , self .ident ),
524520 )
525521
526- def _async_cancel_soon (self ) -> None :
527- """Cancel the browser from the event loop."""
528- self ._async_cancel ()
529- if self ._browser_task :
530- asyncio .ensure_future (self ._async_cancel_browser ())
531-
532522 def cancel (self ) -> None :
533523 """Cancel the browser."""
534524 assert self .zc .loop is not None
535525 assert self .queue is not None
536526 self .queue .put (None )
537- self .zc .loop .call_soon_threadsafe (self ._async_cancel_soon )
527+ self .zc .loop .call_soon_threadsafe (self ._async_cancel )
538528 self .join ()
539529
540530 def run (self ) -> None :
0 commit comments