FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

chore: extend the ordering convention to the remaining large classes … · python-zeroconf/python-zeroconf@a595a5c · GitHub

Repository navigation

Commit a595a5c

Browse files
authored
chore: extend the ordering convention to the remaining large classes (#1874)
1 parent 69fca98 commit a595a5c

5 files changed

Lines changed: 1391 additions & 1391 deletions

File tree

‎src/zeroconf/_cache.py‎

Lines changed: 151 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -61,75 +61,6 @@ def __init__(self) -> None:
6161
# Functions prefixed with async_ are NOT threadsafe and must
6262
# be run in the event loop.
6363

64-
def _async_add(self, record: _DNSRecord) -> bool:
65-
"""Store a record, returning True when it was not cached before.
66-
67-
Event loop only; not threadsafe.
68-
"""
69-
# Previously storage of records was implemented as a list
70-
# instead a dict. Since DNSRecords are now hashable, the implementation
71-
# uses a dict to ensure that adding a new record to the cache
72-
# replaces any existing records that are __eq__ to each other which
73-
# removes the risk that accessing the cache from the wrong
74-
# direction would return the old incorrect entry.
75-
store = self.cache.get(record.key)
76-
is_new = store is None or record not in store
77-
# Bound total cache size; evict closest-to-expiration entry to
78-
# make room before inserting a new record. Prevents a LAN-local
79-
# flood of unique-name records from growing the cache without
80-
# bound (RFC 6762 §10 advisory caching, defense-in-depth).
81-
if is_new and self._total_records >= _MAX_CACHE_RECORDS:
82-
self._async_evict_oldest()
83-
# The victim may have been the last record under
84-
# ``record.key``, in which case ``_remove_key`` deleted
85-
# the bucket. Re-fetch before creating below.
86-
store = self.cache.get(record.key)
87-
if store is None:
88-
store = self.cache[record.key] = {}
89-
new = is_new and not isinstance(record, DNSNsec)
90-
if is_new:
91-
self._total_records += 1
92-
store[record] = record
93-
when = record.created + (record.ttl * 1000)
94-
if self._expirations.get(record) != when:
95-
heappush(self._expire_heap, (when, record))
96-
self._expirations[record] = when
97-
# Re-adds of an existing record with a new TTL push a fresh
98-
# entry but leave the prior tuple behind as stale, so a peer
99-
# that just replays cached records can grow ``_expire_heap``
100-
# without ever tripping the cap. Rebuild when stale entries
101-
# dominate.
102-
self._maybe_rebuild_heap()
103-
104-
if isinstance(record, DNSService):
105-
service_record = record
106-
if (service_store := self.service_cache.get(service_record.server_key)) is None:
107-
service_store = self.service_cache[service_record.server_key] = {}
108-
service_store[service_record] = service_record
109-
return new
110-
111-
def _async_evict_oldest(self) -> None:
112-
"""Drop the closest-to-expiration record to make room for a new one."""
113-
while self._expire_heap:
114-
when_record = heappop(self._expire_heap)
115-
record = when_record[1]
116-
if self._expirations.get(record) != when_record[0]:
117-
continue
118-
self._async_remove(record)
119-
return
120-
121-
def _maybe_rebuild_heap(self) -> None:
122-
"""Rebuild ``_expire_heap`` when stale entries dominate live ones."""
123-
expire_heap_len = len(self._expire_heap)
124-
if (
125-
expire_heap_len > _MIN_SCHEDULED_RECORD_EXPIRATION
126-
and expire_heap_len > len(self._expirations) * 2
127-
):
128-
self._expire_heap = [
129-
entry for entry in self._expire_heap if self._expirations.get(entry[1]) == entry[0]
130-
]
131-
heapify(self._expire_heap)
132-
13364
def async_add_records(self, entries: Iterable[DNSRecord]) -> bool:
13465
"""Add multiple records.
13566
@@ -143,22 +74,33 @@ def async_add_records(self, entries: Iterable[DNSRecord]) -> bool:
14374
new = True
14475
return new
14576

146-
def _async_remove(self, record: _DNSRecord) -> None:
147-
"""Drop a record from the cache. Event loop only; not threadsafe."""
148-
if isinstance(record, DNSService):
149-
service_record = record
150-
_remove_key(self.service_cache, service_record.server_key, service_record)
151-
_remove_key(self.cache, record.key, record)
152-
self._expirations.pop(record, None)
153-
self._total_records -= 1
154-
155-
def async_remove_records(self, entries: Iterable[DNSRecord]) -> None:
156-
"""Remove multiple records.
77+
def async_all_by_details(self, name: _str, type_: _int, class_: _int) -> list[DNSRecord]:
78+
"""Gets all matching entries by details.
15779
158-
This function must be run in from event loop.
80+
This function is not thread-safe and must be called from
81+
the event loop.
15982
"""
160-
for entry in entries:
161-
self._async_remove(entry)
83+
key = name.lower()
84+
records = self.cache.get(key)
85+
matches: list[DNSRecord] = []
86+
if records is None:
87+
return matches
88+
for record in records.values():
89+
if type_ == record.type and class_ == record.class_:
90+
matches.append(record)
91+
return matches
92+
93+
def async_entries_with_name(self, name: str) -> list[DNSRecord]:
94+
"""All cached records for a name; event loop only, not threadsafe."""
95+
return self.entries_with_name(name)
96+
97+
def async_entries_with_server(self, name: str) -> list[DNSRecord]:
98+
"""All cached records for a server name; event loop only, not threadsafe."""
99+
return self.entries_with_server(name)
100+
101+
# The below functions are threadsafe and do not need to be run in the
102+
# event loop, however they all make copies so they significantly
103+
# inefficient.
162104

163105
def async_expire(self, now: _float) -> list[DNSRecord]:
164106
"""Purge expired entries from the cache.
@@ -197,33 +139,53 @@ def async_get_unique(self, entry: _UniqueRecordsType) -> DNSRecord | None:
197139
return None
198140
return store.get(entry)
199141

200-
def async_all_by_details(self, name: _str, type_: _int, class_: _int) -> list[DNSRecord]:
201-
"""Gets all matching entries by details.
142+
def async_mark_unique_records_older_than_1s_to_expire(
143+
self,
144+
unique_types: set[_UniqueType],
145+
answers: Iterable[DNSRecord],
146+
now: _float,
147+
) -> None:
148+
# rfc6762#section-10.2 para 2
149+
# Since unique is set, all old records with that name, rrtype,
150+
# and rrclass that were received more than one second ago are declared
151+
# invalid, and marked to expire from the cache in one second.
152+
answers_rrset = set(answers)
153+
for name, type_, class_ in unique_types:
154+
for record in self.async_all_by_details(name, type_, class_):
155+
created_double = record.created
156+
if (now - created_double > _ONE_SECOND) and record not in answers_rrset:
157+
# Expire in 1s
158+
self._async_set_created_ttl(record, now, 1)
202159

203-
This function is not thread-safe and must be called from
204-
the event loop.
160+
def async_remove_records(self, entries: Iterable[DNSRecord]) -> None:
161+
"""Remove multiple records.
162+
163+
This function must be run in from event loop.
205164
"""
206-
key = name.lower()
207-
records = self.cache.get(key)
208-
matches: list[DNSRecord] = []
209-
if records is None:
210-
return matches
211-
for record in records.values():
212-
if type_ == record.type and class_ == record.class_:
213-
matches.append(record)
214-
return matches
165+
for entry in entries:
166+
self._async_remove(entry)
215167

216-
def async_entries_with_name(self, name: str) -> list[DNSRecord]:
217-
"""All cached records for a name; event loop only, not threadsafe."""
218-
return self.entries_with_name(name)
168+
def current_entry_with_name_and_alias(self, name: str, alias: str) -> DNSRecord | None:
169+
now = current_time_millis()
170+
for record in reversed(self.entries_with_name(name)):
171+
if (
172+
record.type == _TYPE_PTR
173+
and not record.is_expired(now)
174+
and cast(DNSPointer, record).alias == alias
175+
):
176+
return record
177+
return None
219178

220-
def async_entries_with_server(self, name: str) -> list[DNSRecord]:
221-
"""All cached records for a server name; event loop only, not threadsafe."""
222-
return self.entries_with_server(name)
179+
def entries_with_name(self, name: str) -> list[DNSRecord]:
180+
if entries := self.cache.get(name.lower()):
181+
return list(entries.values())
182+
return []
223183

224-
# The below functions are threadsafe and do not need to be run in the
225-
# event loop, however they all make copies so they significantly
226-
# inefficient.
184+
def entries_with_server(self, server: str) -> list[DNSRecord]:
185+
"""All cached records whose server field matches."""
186+
if entries := self.service_cache.get(server.lower()):
187+
return list(entries.values())
188+
return []
227189

228190
def get(self, entry: DNSEntry) -> DNSRecord | None:
229191
if isinstance(entry, _UNIQUE_RECORD_TYPES):
@@ -233,6 +195,14 @@ def get(self, entry: DNSEntry) -> DNSRecord | None:
233195
return cached_entry
234196
return None
235197

198+
def get_all_by_details(self, name: str, type_: _int, class_: _int) -> list[DNSRecord]:
199+
"""Gets all matching entries by details."""
200+
key = name.lower()
201+
records = self.cache.get(key)
202+
if records is None:
203+
return []
204+
return [entry for entry in list(records.values()) if type_ == entry.type and class_ == entry.class_]
205+
236206
def get_by_details(self, name: str, type_: _int, class_: _int) -> DNSRecord | None:
237207
"""Gets the first matching entry by details. Returns None if no entries match.
238208
@@ -254,61 +224,91 @@ def get_by_details(self, name: str, type_: _int, class_: _int) -> DNSRecord | No
254224
return cached_entry
255225
return None
256226

257-
def get_all_by_details(self, name: str, type_: _int, class_: _int) -> list[DNSRecord]:
258-
"""Gets all matching entries by details."""
259-
key = name.lower()
260-
records = self.cache.get(key)
261-
if records is None:
262-
return []
263-
return [entry for entry in list(records.values()) if type_ == entry.type and class_ == entry.class_]
227+
def names(self) -> list[str]:
228+
"""Return a copy of the list of current cache names."""
229+
return list(self.cache)
264230

265-
def entries_with_server(self, server: str) -> list[DNSRecord]:
266-
"""All cached records whose server field matches."""
267-
if entries := self.service_cache.get(server.lower()):
268-
return list(entries.values())
269-
return []
231+
def _async_add(self, record: _DNSRecord) -> bool:
232+
"""Store a record, returning True when it was not cached before.
270233
271-
def entries_with_name(self, name: str) -> list[DNSRecord]:
272-
if entries := self.cache.get(name.lower()):
273-
return list(entries.values())
274-
return []
234+
Event loop only; not threadsafe.
235+
"""
236+
# Previously storage of records was implemented as a list
237+
# instead a dict. Since DNSRecords are now hashable, the implementation
238+
# uses a dict to ensure that adding a new record to the cache
239+
# replaces any existing records that are __eq__ to each other which
240+
# removes the risk that accessing the cache from the wrong
241+
# direction would return the old incorrect entry.
242+
store = self.cache.get(record.key)
243+
is_new = store is None or record not in store
244+
# Bound total cache size; evict closest-to-expiration entry to
245+
# make room before inserting a new record. Prevents a LAN-local
246+
# flood of unique-name records from growing the cache without
247+
# bound (RFC 6762 §10 advisory caching, defense-in-depth).
248+
if is_new and self._total_records >= _MAX_CACHE_RECORDS:
249+
self._async_evict_oldest()
250+
# The victim may have been the last record under
251+
# ``record.key``, in which case ``_remove_key`` deleted
252+
# the bucket. Re-fetch before creating below.
253+
store = self.cache.get(record.key)
254+
if store is None:
255+
store = self.cache[record.key] = {}
256+
new = is_new and not isinstance(record, DNSNsec)
257+
if is_new:
258+
self._total_records += 1
259+
store[record] = record
260+
when = record.created + (record.ttl * 1000)
261+
if self._expirations.get(record) != when:
262+
heappush(self._expire_heap, (when, record))
263+
self._expirations[record] = when
264+
# Re-adds of an existing record with a new TTL push a fresh
265+
# entry but leave the prior tuple behind as stale, so a peer
266+
# that just replays cached records can grow ``_expire_heap``
267+
# without ever tripping the cap. Rebuild when stale entries
268+
# dominate.
269+
self._maybe_rebuild_heap()
275270

276-
def current_entry_with_name_and_alias(self, name: str, alias: str) -> DNSRecord | None:
277-
now = current_time_millis()
278-
for record in reversed(self.entries_with_name(name)):
279-
if (
280-
record.type == _TYPE_PTR
281-
and not record.is_expired(now)
282-
and cast(DNSPointer, record).alias == alias
283-
):
284-
return record
285-
return None
271+
if isinstance(record, DNSService):
272+
service_record = record
273+
if (service_store := self.service_cache.get(service_record.server_key)) is None:
274+
service_store = self.service_cache[service_record.server_key] = {}
275+
service_store[service_record] = service_record
276+
return new
286277

287-
def names(self) -> list[str]:
288-
"""Return a copy of the list of current cache names."""
289-
return list(self.cache)
278+
def _async_evict_oldest(self) -> None:
279+
"""Drop the closest-to-expiration record to make room for a new one."""
280+
while self._expire_heap:
281+
when_record = heappop(self._expire_heap)
282+
record = when_record[1]
283+
if self._expirations.get(record) != when_record[0]:
284+
continue
285+
self._async_remove(record)
286+
return
290287

291-
def async_mark_unique_records_older_than_1s_to_expire(
292-
self,
293-
unique_types: set[_UniqueType],
294-
answers: Iterable[DNSRecord],
295-
now: _float,
296-
) -> None:
297-
# rfc6762#section-10.2 para 2
298-
# Since unique is set, all old records with that name, rrtype,
299-
# and rrclass that were received more than one second ago are declared
300-
# invalid, and marked to expire from the cache in one second.
301-
answers_rrset = set(answers)
302-
for name, type_, class_ in unique_types:
303-
for record in self.async_all_by_details(name, type_, class_):
304-
created_double = record.created
305-
if (now - created_double > _ONE_SECOND) and record not in answers_rrset:
306-
# Expire in 1s
307-
self._async_set_created_ttl(record, now, 1)
288+
def _async_remove(self, record: _DNSRecord) -> None:
289+
"""Drop a record from the cache. Event loop only; not threadsafe."""
290+
if isinstance(record, DNSService):
291+
service_record = record
292+
_remove_key(self.service_cache, service_record.server_key, service_record)
293+
_remove_key(self.cache, record.key, record)
294+
self._expirations.pop(record, None)
295+
self._total_records -= 1
308296

309297
def _async_set_created_ttl(self, record: DNSRecord, now: _float, ttl: _int) -> None:
310298
"""Stamp a record with a new ttl and creation moment, then cache it."""
311299
# It would be better if we made a copy instead of mutating the record
312300
# in place, but records currently don't have a copy method.
313301
record._set_created_ttl(now, ttl)
314302
self._async_add(record)
303+
304+
def _maybe_rebuild_heap(self) -> None:
305+
"""Rebuild ``_expire_heap`` when stale entries dominate live ones."""
306+
expire_heap_len = len(self._expire_heap)
307+
if (
308+
expire_heap_len > _MIN_SCHEDULED_RECORD_EXPIRATION
309+
and expire_heap_len > len(self._expirations) * 2
310+
):
311+
self._expire_heap = [
312+
entry for entry in self._expire_heap if self._expirations.get(entry[1]) == entry[0]
313+
]
314+
heapify(self._expire_heap)

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL