Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 111c91a

Browse filesBrowse files
authored
feat: improve performance when IP addresses change frequently (#1407)
1 parent 30f85fd commit 111c91a
Copy full SHA for 111c91a

File tree

3 files changed

+39
-33
lines changed
Filter options

3 files changed

+39
-33
lines changed

‎src/zeroconf/_services/info.py

Copy file name to clipboardExpand all lines: src/zeroconf/_services/info.py
+24-15Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import asyncio
2424
import random
2525
import sys
26-
from ipaddress import IPv4Address, IPv6Address, _BaseAddress
2726
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union, cast
2827

2928
from .._cache import DNSCache
@@ -50,6 +49,8 @@
5049
wait_for_future_set_or_timeout,
5150
)
5251
from .._utils.ipaddress import (
52+
ZeroconfIPv4Address,
53+
ZeroconfIPv6Address,
5354
cached_ip_addresses,
5455
get_ip_address_object_from_record,
5556
ip_bytes_and_scope_to_address,
@@ -187,8 +188,8 @@ def __init__(
187188
self.type = type_
188189
self._name = name
189190
self.key = name.lower()
190-
self._ipv4_addresses: List[IPv4Address] = []
191-
self._ipv6_addresses: List[IPv6Address] = []
191+
self._ipv4_addresses: List[ZeroconfIPv4Address] = []
192+
self._ipv6_addresses: List[ZeroconfIPv6Address] = []
192193
if addresses is not None:
193194
self.addresses = addresses
194195
elif parsed_addresses is not None:
@@ -260,11 +261,11 @@ def addresses(self, value: List[bytes]) -> None:
260261
)
261262
if addr.version == 4:
262263
if TYPE_CHECKING:
263-
assert isinstance(addr, IPv4Address)
264+
assert isinstance(addr, ZeroconfIPv4Address)
264265
self._ipv4_addresses.append(addr)
265266
else:
266267
if TYPE_CHECKING:
267-
assert isinstance(addr, IPv6Address)
268+
assert isinstance(addr, ZeroconfIPv6Address)
268269
self._ipv6_addresses.append(addr)
269270

270271
@property
@@ -321,7 +322,7 @@ def addresses_by_version(self, version: IPVersion) -> List[bytes]:
321322

322323
def ip_addresses_by_version(
323324
self, version: IPVersion
324-
) -> Union[List[IPv4Address], List[IPv6Address], List[_BaseAddress]]:
325+
) -> Union[List[ZeroconfIPv4Address], List[ZeroconfIPv6Address]]:
325326
"""List ip_address objects matching IP version.
326327
327328
Addresses are guaranteed to be returned in LIFO (last in, first out)
@@ -334,7 +335,7 @@ def ip_addresses_by_version(
334335

335336
def _ip_addresses_by_version_value(
336337
self, version_value: int_
337-
) -> Union[List[IPv4Address], List[IPv6Address]]:
338+
) -> Union[List[ZeroconfIPv4Address], List[ZeroconfIPv6Address]]:
338339
"""Backend for addresses_by_version that uses the raw value."""
339340
if version_value == _IPVersion_All_value:
340341
return [*self._ipv4_addresses, *self._ipv6_addresses] # type: ignore[return-value]
@@ -440,9 +441,9 @@ def get_name(self) -> str:
440441

441442
def _get_ip_addresses_from_cache_lifo(
442443
self, zc: "Zeroconf", now: float_, type: int_
443-
) -> List[Union[IPv4Address, IPv6Address]]:
444+
) -> List[Union[ZeroconfIPv4Address, ZeroconfIPv6Address]]:
444445
"""Set IPv6 addresses from the cache."""
445-
address_list: List[Union[IPv4Address, IPv6Address]] = []
446+
address_list: List[Union[ZeroconfIPv4Address, ZeroconfIPv6Address]] = []
446447
for record in self._get_address_records_from_cache_by_type(zc, type):
447448
if record.is_expired(now):
448449
continue
@@ -456,7 +457,7 @@ def _set_ipv6_addresses_from_cache(self, zc: "Zeroconf", now: float_) -> None:
456457
"""Set IPv6 addresses from the cache."""
457458
if TYPE_CHECKING:
458459
self._ipv6_addresses = cast(
459-
"List[IPv6Address]",
460+
"List[ZeroconfIPv6Address]",
460461
self._get_ip_addresses_from_cache_lifo(zc, now, _TYPE_AAAA),
461462
)
462463
else:
@@ -466,7 +467,7 @@ def _set_ipv4_addresses_from_cache(self, zc: "Zeroconf", now: float_) -> None:
466467
"""Set IPv4 addresses from the cache."""
467468
if TYPE_CHECKING:
468469
self._ipv4_addresses = cast(
469-
"List[IPv4Address]",
470+
"List[ZeroconfIPv4Address]",
470471
self._get_ip_addresses_from_cache_lifo(zc, now, _TYPE_A),
471472
)
472473
else:
@@ -509,24 +510,32 @@ def _process_record_threadsafe(self, zc: "Zeroconf", record: DNSRecord, now: flo
509510

510511
if ip_addr.version == 4:
511512
if TYPE_CHECKING:
512-
assert isinstance(ip_addr, IPv4Address)
513+
assert isinstance(ip_addr, ZeroconfIPv4Address)
513514
ipv4_addresses = self._ipv4_addresses
514515
if ip_addr not in ipv4_addresses:
515516
ipv4_addresses.insert(0, ip_addr)
516517
return True
517-
elif ip_addr != ipv4_addresses[0]:
518+
# Use int() to compare the addresses as integers
519+
# since by default IPv4Address.__eq__ compares the
520+
# the addresses on version and int which more than
521+
# we need here since we know the version is 4.
522+
elif ip_addr.zc_integer != ipv4_addresses[0].zc_integer:
518523
ipv4_addresses.remove(ip_addr)
519524
ipv4_addresses.insert(0, ip_addr)
520525

521526
return False
522527

523528
if TYPE_CHECKING:
524-
assert isinstance(ip_addr, IPv6Address)
529+
assert isinstance(ip_addr, ZeroconfIPv6Address)
525530
ipv6_addresses = self._ipv6_addresses
526531
if ip_addr not in self._ipv6_addresses:
527532
ipv6_addresses.insert(0, ip_addr)
528533
return True
529-
elif ip_addr != self._ipv6_addresses[0]:
534+
# Use int() to compare the addresses as integers
535+
# since by default IPv6Address.__eq__ compares the
536+
# the addresses on version and int which more than
537+
# we need here since we know the version is 6.
538+
elif ip_addr.zc_integer != self._ipv6_addresses[0].zc_integer:
530539
ipv6_addresses.remove(ip_addr)
531540
ipv6_addresses.insert(0, ip_addr)
532541

‎src/zeroconf/_utils/ipaddress.py

Copy file name to clipboardExpand all lines: src/zeroconf/_utils/ipaddress.py
+10-18Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,7 @@
3939

4040

4141
class ZeroconfIPv4Address(IPv4Address):
42-
__slots__ = (
43-
"_str",
44-
"_is_link_local",
45-
"_is_unspecified",
46-
"_is_loopback",
47-
"__hash__",
48-
)
42+
__slots__ = ("_str", "_is_link_local", "_is_unspecified", "_is_loopback", "__hash__", "zc_integer")
4943

5044
def __init__(self, *args: Any, **kwargs: Any) -> None:
5145
"""Initialize a new IPv4 address."""
@@ -55,6 +49,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
5549
self._is_unspecified = super().is_unspecified
5650
self._is_loopback = super().is_loopback
5751
self.__hash__ = cache(lambda: IPv4Address.__hash__(self)) # type: ignore[method-assign]
52+
self.zc_integer = int(self)
5853

5954
def __str__(self) -> str:
6055
"""Return the string representation of the IPv4 address."""
@@ -77,13 +72,7 @@ def is_loopback(self) -> bool:
7772

7873

7974
class ZeroconfIPv6Address(IPv6Address):
80-
__slots__ = (
81-
"_str",
82-
"_is_link_local",
83-
"_is_unspecified",
84-
"_is_loopback",
85-
"__hash__",
86-
)
75+
__slots__ = ("_str", "_is_link_local", "_is_unspecified", "_is_loopback", "__hash__", "zc_integer")
8776

8877
def __init__(self, *args: Any, **kwargs: Any) -> None:
8978
"""Initialize a new IPv6 address."""
@@ -93,6 +82,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
9382
self._is_unspecified = super().is_unspecified
9483
self._is_loopback = super().is_loopback
9584
self.__hash__ = cache(lambda: IPv6Address.__hash__(self)) # type: ignore[method-assign]
85+
self.zc_integer = int(self)
9686

9787
def __str__(self) -> str:
9888
"""Return the string representation of the IPv6 address."""
@@ -117,7 +107,7 @@ def is_loopback(self) -> bool:
117107
@lru_cache(maxsize=512)
118108
def _cached_ip_addresses(
119109
address: Union[str, bytes, int],
120-
) -> Optional[Union[IPv4Address, IPv6Address]]:
110+
) -> Optional[Union[ZeroconfIPv4Address, ZeroconfIPv6Address]]:
121111
"""Cache IP addresses."""
122112
try:
123113
return ZeroconfIPv4Address(address)
@@ -136,14 +126,16 @@ def _cached_ip_addresses(
136126

137127
def get_ip_address_object_from_record(
138128
record: DNSAddress,
139-
) -> Optional[Union[IPv4Address, IPv6Address]]:
129+
) -> Optional[Union[ZeroconfIPv4Address, ZeroconfIPv6Address]]:
140130
"""Get the IP address object from the record."""
141131
if IPADDRESS_SUPPORTS_SCOPE_ID and record.type == _TYPE_AAAA and record.scope_id:
142132
return ip_bytes_and_scope_to_address(record.address, record.scope_id)
143133
return cached_ip_addresses_wrapper(record.address)
144134

145135

146-
def ip_bytes_and_scope_to_address(address: bytes_, scope: int_) -> Optional[Union[IPv4Address, IPv6Address]]:
136+
def ip_bytes_and_scope_to_address(
137+
address: bytes_, scope: int_
138+
) -> Optional[Union[ZeroconfIPv4Address, ZeroconfIPv6Address]]:
147139
"""Convert the bytes and scope to an IP address object."""
148140
base_address = cached_ip_addresses_wrapper(address)
149141
if base_address is not None and base_address.is_link_local:
@@ -152,7 +144,7 @@ def ip_bytes_and_scope_to_address(address: bytes_, scope: int_) -> Optional[Unio
152144
return base_address
153145

154146

155-
def str_without_scope_id(addr: Union[IPv4Address, IPv6Address]) -> str:
147+
def str_without_scope_id(addr: Union[ZeroconfIPv4Address, ZeroconfIPv6Address]) -> str:
156148
"""Return the string representation of the address without the scope id."""
157149
if IPADDRESS_SUPPORTS_SCOPE_ID and addr.version == 6:
158150
address_str = str(addr)

‎tests/services/test_info.py

Copy file name to clipboardExpand all lines: tests/services/test_info.py
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,10 @@ async def test_ipv6_changes_are_seen():
14691469
assert info.addresses_by_version(IPVersion.V6Only) == [
14701470
b"\xde\xad\xbe\xef\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
14711471
]
1472+
info.load_from_cache(aiozc.zeroconf)
1473+
assert info.addresses_by_version(IPVersion.V6Only) == [
1474+
b"\xde\xad\xbe\xef\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1475+
]
14721476

14731477
generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
14741478
generated.add_answer_at_time(
@@ -1494,6 +1498,7 @@ async def test_ipv6_changes_are_seen():
14941498
b"\x00\xad\xbe\xef\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
14951499
b"\xde\xad\xbe\xef\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
14961500
]
1501+
14971502
await aiozc.async_close()
14981503

14991504

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.