Skip to content

Commit b65e279

Browse files
authored
feat: improve performance of processing incoming records (#1155)
1 parent 6abeb78 commit b65e279

5 files changed

Lines changed: 32 additions & 29 deletions

File tree

src/zeroconf/_dns.pxd

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ cdef object _RECENT_TIME_MS
1616
cdef object _CLASS_UNIQUE
1717
cdef object _CLASS_MASK
1818

19+
cdef object current_time_millis
20+
1921
cdef class DNSEntry:
2022

2123
cdef public object key
@@ -96,8 +98,11 @@ cdef class DNSNsec(DNSRecord):
9698

9799
cdef class DNSRRSet:
98100

99-
cdef _records
101+
cdef _record_sets
100102
cdef cython.dict _lookup
101103

102104
@cython.locals(other=DNSRecord)
103105
cpdef suppresses(self, DNSRecord record)
106+
107+
@cython.locals(lookup=cython.dict)
108+
cdef _get_lookup(self)

src/zeroconf/_dns.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -512,31 +512,30 @@ def __repr__(self) -> str:
512512

513513

514514
class DNSRRSet:
515-
"""A set of dns records independent of the ttl."""
515+
"""A set of dns records with a lookup to get the ttl."""
516516

517-
__slots__ = ('_records', '_lookup')
517+
__slots__ = ('_record_sets', '_lookup')
518518

519-
def __init__(self, records: Iterable[DNSRecord]) -> None:
520-
"""Create an RRset from records."""
521-
self._records = records
522-
self._lookup: Optional[Dict[DNSRecord, DNSRecord]] = None
519+
def __init__(self, record_sets: Iterable[List[DNSRecord]]) -> None:
520+
"""Create an RRset from records sets."""
521+
self._record_sets = record_sets
522+
self._lookup: Optional[Dict[DNSRecord, float]] = None
523523

524524
@property
525-
def lookup(self) -> Dict[DNSRecord, DNSRecord]:
525+
def lookup(self) -> Dict[DNSRecord, float]:
526+
"""Return the lookup table."""
527+
return self._get_lookup()
528+
529+
def _get_lookup(self) -> Dict[DNSRecord, float]:
530+
"""Return the lookup table, building it if needed."""
526531
if self._lookup is None:
527-
# Build the hash table so we can lookup the record independent of the ttl
528-
self._lookup = {record: record for record in self._records}
532+
# Build the hash table so we can lookup the record ttl
533+
self._lookup = {record: record.ttl for record_sets in self._record_sets for record in record_sets}
529534
return self._lookup
530535

531536
def suppresses(self, record: _DNSRecord) -> bool:
532537
"""Returns true if any answer in the rrset can suffice for the
533538
information held in this record."""
534-
if self._lookup is None:
535-
other = self.lookup.get(record)
536-
else:
537-
other = self._lookup.get(record)
538-
return bool(other and other.ttl > (record.ttl / 2))
539-
540-
def __contains__(self, record: DNSRecord) -> bool:
541-
"""Returns true if the rrset contains the record."""
542-
return record in self.lookup
539+
lookup = self._get_lookup()
540+
other_ttl = lookup.get(record)
541+
return bool(other_ttl and other_ttl > (record.ttl / 2))

src/zeroconf/_handlers.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,6 @@ class AnswerGroup(NamedTuple):
9090
answers: _AnswerWithAdditionalsType
9191

9292

93-
def _message_is_probe(msg: DNSIncoming) -> bool:
94-
return msg.num_authorities > 0
95-
96-
9793
def construct_nsec_record(name: str, types: List[int], now: float) -> DNSNsec:
9894
"""Construct an NSEC record for name and a list of dns types.
9995
@@ -159,7 +155,7 @@ class _QueryResponse:
159155

160156
def __init__(self, cache: DNSCache, msgs: List[DNSIncoming]) -> None:
161157
"""Build a query response."""
162-
self._is_probe = any(_message_is_probe(msg) for msg in msgs)
158+
self._is_probe = any(msg.is_probe for msg in msgs)
163159
self._msg = msgs[0]
164160
self._now = self._msg.now
165161
self._cache = cache
@@ -363,9 +359,7 @@ def async_response( # pylint: disable=unused-argument
363359
This function must be run in the event loop as it is not
364360
threadsafe.
365361
"""
366-
known_answers = DNSRRSet(
367-
itertools.chain.from_iterable(msg.answers for msg in msgs if not _message_is_probe(msg))
368-
)
362+
known_answers = DNSRRSet(msg.answers for msg in msgs if not msg.is_probe)
369363
query_res = _QueryResponse(self.cache, msgs)
370364

371365
for msg in msgs:

src/zeroconf/_protocol/incoming.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ def answers(self) -> List[DNSRecord]:
174174
)
175175
return self._answers
176176

177+
@property
178+
def is_probe(self) -> bool:
179+
"""Returns true if this is a probe."""
180+
return self.num_authorities > 0
181+
177182
def __repr__(self) -> str:
178183
return '<DNSIncoming:{%s}>' % ', '.join(
179184
[

tests/test_dns.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ def test_rrset_does_not_consider_ttl():
392392
longaaaarec = r.DNSAddress('irrelevant', const._TYPE_AAAA, const._CLASS_IN, 100, b'same')
393393
shortaaaarec = r.DNSAddress('irrelevant', const._TYPE_AAAA, const._CLASS_IN, 10, b'same')
394394

395-
rrset = DNSRRSet([longarec, shortaaaarec])
395+
rrset = DNSRRSet([[longarec, shortaaaarec]])
396396

397397
assert rrset.suppresses(longarec)
398398
assert rrset.suppresses(shortarec)
@@ -404,7 +404,7 @@ def test_rrset_does_not_consider_ttl():
404404
mediumarec = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 60, b'same')
405405
shortarec = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 10, b'same')
406406

407-
rrset2 = DNSRRSet([mediumarec])
407+
rrset2 = DNSRRSet([[mediumarec]])
408408
assert not rrset2.suppresses(verylongarec)
409409
assert rrset2.suppresses(longarec)
410410
assert rrset2.suppresses(mediumarec)

0 commit comments

Comments
 (0)