feat: improve performance when IP addresses change frequently (#1407) · python-zeroconf/python-zeroconf@111c91a

@@ -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

28272928

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)

269270270271

@property

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

321322322323

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.

326327327328

Addresses are guaranteed to be returned in LIFO (last in, first out)

@@ -334,7 +335,7 @@ def ip_addresses_by_version(

334335335336

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:

440441441442

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

509510510511

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)

520525521526

return False

522527523528

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