Skip to content

Commit d2af6a0

Browse files
authored
feat: speed up record updates (#1301)
1 parent a910a2b commit d2af6a0

11 files changed

Lines changed: 78 additions & 32 deletions

File tree

build_ext.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def build(setup_kwargs: Any) -> None:
2626
"src/zeroconf/_dns.py",
2727
"src/zeroconf/_cache.py",
2828
"src/zeroconf/_history.py",
29+
"src/zeroconf/_record_update.py",
2930
"src/zeroconf/_listener.py",
3031
"src/zeroconf/_protocol/incoming.py",
3132
"src/zeroconf/_protocol/outgoing.py",

src/zeroconf/_cache.pxd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ cdef class DNSCache:
4444
)
4545
cpdef async_all_by_details(self, str name, object type_, object class_)
4646

47-
cpdef async_entries_with_name(self, str name)
47+
cpdef cython.dict async_entries_with_name(self, str name)
4848

49-
cpdef async_entries_with_server(self, str name)
49+
cpdef cython.dict async_entries_with_server(self, str name)
5050

5151
@cython.locals(
5252
cached_entry=DNSRecord,
@@ -57,7 +57,7 @@ cdef class DNSCache:
5757
records=cython.dict,
5858
entry=DNSRecord,
5959
)
60-
cpdef get_all_by_details(self, str name, object type_, object class_)
60+
cpdef cython.list get_all_by_details(self, str name, object type_, object class_)
6161

6262
@cython.locals(
6363
store=cython.dict,

src/zeroconf/_handlers/query_handler.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def _has_mcast_within_one_quarter_ttl(self, record: DNSRecord) -> bool:
139139
if TYPE_CHECKING:
140140
record = cast(_UniqueRecordsType, record)
141141
maybe_entry = self._cache.async_get_unique(record)
142-
return bool(maybe_entry and maybe_entry.is_recent(self._now))
142+
return bool(maybe_entry is not None and maybe_entry.is_recent(self._now) is True)
143143

144144
def _has_mcast_record_in_last_second(self, record: DNSRecord) -> bool:
145145
"""Check if an answer was seen in the last second.
@@ -149,7 +149,7 @@ def _has_mcast_record_in_last_second(self, record: DNSRecord) -> bool:
149149
if TYPE_CHECKING:
150150
record = cast(_UniqueRecordsType, record)
151151
maybe_entry = self._cache.async_get_unique(record)
152-
return bool(maybe_entry and self._now - maybe_entry.created < _ONE_SECOND)
152+
return bool(maybe_entry is not None and self._now - maybe_entry.created < _ONE_SECOND)
153153

154154

155155
class QueryHandler:
@@ -174,7 +174,7 @@ def _add_service_type_enumeration_query_answers(
174174
dns_pointer = DNSPointer(
175175
_SERVICE_TYPE_ENUMERATION_NAME, _TYPE_PTR, _CLASS_IN, _DNS_OTHER_TTL, stype, 0.0
176176
)
177-
if not known_answers.suppresses(dns_pointer):
177+
if known_answers.suppresses(dns_pointer) is False:
178178
answer_set[dns_pointer] = set()
179179

180180
def _add_pointer_answers(
@@ -185,7 +185,7 @@ def _add_pointer_answers(
185185
# Add recommended additional answers according to
186186
# https://tools.ietf.org/html/rfc6763#section-12.1.
187187
dns_pointer = service._dns_pointer(None)
188-
if known_answers.suppresses(dns_pointer):
188+
if known_answers.suppresses(dns_pointer) is True:
189189
continue
190190
answer_set[dns_pointer] = {
191191
service._dns_service(None),
@@ -208,7 +208,7 @@ def _add_address_answers(
208208
seen_types.add(dns_address.type)
209209
if dns_address.type != type_:
210210
additionals.add(dns_address)
211-
elif not known_answers.suppresses(dns_address):
211+
elif known_answers.suppresses(dns_address) is False:
212212
answers.append(dns_address)
213213
missing_types: Set[int] = _ADDRESS_RECORD_TYPES - seen_types
214214
if answers:
@@ -248,11 +248,11 @@ def _answer_question(
248248
# Add recommended additional answers according to
249249
# https://tools.ietf.org/html/rfc6763#section-12.2.
250250
dns_service = service._dns_service(None)
251-
if not known_answers.suppresses(dns_service):
251+
if known_answers.suppresses(dns_service) is False:
252252
answer_set[dns_service] = service._get_address_and_nsec_records(None)
253253
if type_ in (_TYPE_TXT, _TYPE_ANY):
254254
dns_text = service._dns_text(None)
255-
if not known_answers.suppresses(dns_text):
255+
if known_answers.suppresses(dns_text) is False:
256256
answer_set[dns_text] = set()
257257

258258
return answer_set

src/zeroconf/_record_update.pxd

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
import cython
3+
4+
from ._dns cimport DNSRecord
5+
6+
7+
cdef class RecordUpdate:
8+
9+
cdef public DNSRecord new
10+
cdef public DNSRecord old

src/zeroconf/_record_update.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,24 @@
2020
USA
2121
"""
2222

23-
from typing import NamedTuple, Optional
23+
from typing import Optional
2424

2525
from ._dns import DNSRecord
2626

2727

28-
class RecordUpdate(NamedTuple):
29-
new: DNSRecord
30-
old: Optional[DNSRecord]
28+
class RecordUpdate:
29+
30+
__slots__ = ("new", "old")
31+
32+
def __init__(self, new: DNSRecord, old: Optional[DNSRecord] = None):
33+
"""RecordUpdate represents a change in a DNS record."""
34+
self.new = new
35+
self.old = old
36+
37+
def __getitem__(self, index: int) -> Optional[DNSRecord]:
38+
"""Get the new or old record."""
39+
if index == 0:
40+
return self.new
41+
elif index == 1:
42+
return self.old
43+
raise IndexError(index)

src/zeroconf/_services/browser.pxd

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import cython
33

44
from .._cache cimport DNSCache
55
from .._protocol.outgoing cimport DNSOutgoing, DNSPointer, DNSQuestion, DNSRecord
6+
from .._record_update cimport RecordUpdate
67
from .._updates cimport RecordUpdateListener
78
from .._utils.time cimport current_time_millis, millis_to_seconds
89
from . cimport Signal, SignalRegistrationInterface
@@ -13,6 +14,7 @@ cdef object cached_possible_types
1314
cdef cython.uint _EXPIRE_REFRESH_TIME_PERCENT
1415
cdef cython.uint _TYPE_PTR
1516
cdef object SERVICE_STATE_CHANGE_ADDED, SERVICE_STATE_CHANGE_REMOVED, SERVICE_STATE_CHANGE_UPDATED
17+
cdef cython.set _ADDRESS_RECORD_TYPES
1618

1719
cdef class _DNSPointerOutgoingBucket:
1820

@@ -43,6 +45,7 @@ cdef class _ServiceBrowserBase(RecordUpdateListener):
4345

4446
cdef public cython.set types
4547
cdef public object zc
48+
cdef DNSCache _cache
4649
cdef object _loop
4750
cdef public object addr
4851
cdef public object port
@@ -60,10 +63,10 @@ cdef class _ServiceBrowserBase(RecordUpdateListener):
6063

6164
cpdef _enqueue_callback(self, object state_change, object type_, object name)
6265

63-
@cython.locals(record=DNSRecord, cache=DNSCache, service=DNSRecord, pointer=DNSPointer)
66+
@cython.locals(record_update=RecordUpdate, record=DNSRecord, cache=DNSCache, service=DNSRecord, pointer=DNSPointer)
6467
cpdef async_update_records(self, object zc, cython.float now, cython.list records)
6568

66-
cpdef _names_matching_types(self, object types)
69+
cpdef cython.list _names_matching_types(self, object types)
6770

6871
cpdef reschedule_type(self, object type_, object now, object next_time)
6972

src/zeroconf/_services/browser.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ class _ServiceBrowserBase(RecordUpdateListener):
297297
__slots__ = (
298298
'types',
299299
'zc',
300+
'_cache',
300301
'_loop',
301302
'addr',
302303
'port',
@@ -345,6 +346,7 @@ def __init__(
345346
# Will generate BadTypeInNameException on a bad name
346347
service_type_name(check_type_, strict=False)
347348
self.zc = zc
349+
self._cache = zc.cache
348350
assert zc.loop is not None
349351
self._loop = zc.loop
350352
self.addr = addr
@@ -421,8 +423,8 @@ def async_update_records(self, zc: 'Zeroconf', now: float_, records: List[Record
421423
This method will be run in the event loop.
422424
"""
423425
for record_update in records:
424-
record = record_update[0]
425-
old_record = record_update[1]
426+
record = record_update.new
427+
old_record = record_update.old
426428
record_type = record.type
427429

428430
if record_type is _TYPE_PTR:
@@ -440,15 +442,14 @@ def async_update_records(self, zc: 'Zeroconf', now: float_, records: List[Record
440442
continue
441443

442444
# If its expired or already exists in the cache it cannot be updated.
443-
if old_record or record.is_expired(now) is True:
445+
if old_record is not None or record.is_expired(now) is True:
444446
continue
445447

446448
if record_type in _ADDRESS_RECORD_TYPES:
447-
cache = self.zc.cache
449+
cache = self._cache
450+
names = {service.name for service in cache.async_entries_with_server(record.name)}
448451
# Iterate through the DNSCache and callback any services that use this address
449-
for type_, name in self._names_matching_types(
450-
{service.name for service in cache.async_entries_with_server(record.name)}
451-
):
452+
for type_, name in self._names_matching_types(names):
452453
self._enqueue_callback(SERVICE_STATE_CHANGE_UPDATED, type_, name)
453454
continue
454455

src/zeroconf/_services/info.pxd

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import cython
44
from .._cache cimport DNSCache
55
from .._dns cimport DNSAddress, DNSNsec, DNSPointer, DNSRecord, DNSService, DNSText
66
from .._protocol.outgoing cimport DNSOutgoing
7+
from .._record_update cimport RecordUpdate
78
from .._updates cimport RecordUpdateListener
89
from .._utils.time cimport current_time_millis
910

@@ -56,7 +57,7 @@ cdef class ServiceInfo(RecordUpdateListener):
5657
cdef public cython.list _dns_address_cache
5758
cdef public cython.set _get_address_and_nsec_records_cache
5859

59-
@cython.locals(cache=DNSCache)
60+
@cython.locals(record_update=RecordUpdate, update=bint, cache=DNSCache)
6061
cpdef async_update_records(self, object zc, cython.float now, cython.list records)
6162

6263
@cython.locals(cache=DNSCache)
@@ -76,7 +77,7 @@ cdef class ServiceInfo(RecordUpdateListener):
7677
dns_text_record=DNSText,
7778
dns_address_record=DNSAddress
7879
)
79-
cdef _process_record_threadsafe(self, object zc, DNSRecord record, cython.float now)
80+
cdef bint _process_record_threadsafe(self, object zc, DNSRecord record, cython.float now)
8081

8182
@cython.locals(cache=DNSCache)
8283
cdef cython.list _get_address_records_from_cache_by_type(self, object zc, object _type)
@@ -109,3 +110,6 @@ cdef class ServiceInfo(RecordUpdateListener):
109110
cdef cython.set _get_address_and_nsec_records(self, object override_ttl)
110111

111112
cpdef async_clear_cache(self)
113+
114+
@cython.locals(cache=DNSCache)
115+
cdef _generate_request_query(self, object zc, object now, object question_type)

src/zeroconf/_services/info.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ def _get_ip_addresses_from_cache_lifo(
420420
"""Set IPv6 addresses from the cache."""
421421
address_list: List[Union[IPv4Address, IPv6Address]] = []
422422
for record in self._get_address_records_from_cache_by_type(zc, type):
423-
if record.is_expired(now):
423+
if record.is_expired(now) is True:
424424
continue
425425
ip_addr = _cached_ip_addresses_wrapper(record.address)
426426
if ip_addr is not None:
@@ -463,7 +463,7 @@ def _process_record_threadsafe(self, zc: 'Zeroconf', record: DNSRecord, now: flo
463463
464464
Returns True if a new record was added.
465465
"""
466-
if record.is_expired(now):
466+
if record.is_expired(now) is True:
467467
return False
468468

469469
record_key = record.key
@@ -779,7 +779,7 @@ async def async_request(
779779

780780
now = current_time_millis()
781781

782-
if self._load_from_cache(zc, now):
782+
if self._load_from_cache(zc, now) is True:
783783
return True
784784

785785
if TYPE_CHECKING:
@@ -795,7 +795,7 @@ async def async_request(
795795
if last <= now:
796796
return False
797797
if next_ <= now:
798-
out = self.generate_request_query(
798+
out = self._generate_request_query(
799799
zc,
800800
now,
801801
question_type or DNS_QUESTION_TYPE_QU if first_request else DNS_QUESTION_TYPE_QM,
@@ -815,8 +815,8 @@ async def async_request(
815815

816816
return True
817817

818-
def generate_request_query(
819-
self, zc: 'Zeroconf', now: float_, question_type: Optional[DNSQuestionType] = None
818+
def _generate_request_query(
819+
self, zc: 'Zeroconf', now: float_, question_type: DNSQuestionType
820820
) -> DNSOutgoing:
821821
"""Generate the request query."""
822822
out = DNSOutgoing(_FLAGS_QR_QUERY)

src/zeroconf/_updates.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def async_update_records(self, zc: 'Zeroconf', now: float_, records: List[Record
6868
This method will be run in the event loop.
6969
"""
7070
for record in records:
71-
self.update_record(zc, now, record[0])
71+
self.update_record(zc, now, record.new)
7272

7373
def async_update_records_complete(self) -> None:
7474
"""Called when a record update has completed for all handlers.

0 commit comments

Comments
 (0)