Pre-filter zeroconf service browser updates (#35518)

Each ServerBrowser currently runs in its own thread which
processes every A or AAAA record update per instance.

As the list of zeroconf names we watch for grows, each additional
ServiceBrowser would process all the A and AAAA updates on the network.

To avoid overwhemling the system we pre-filter here and only process
DNSPointers for the configured record name (type)
pull/35523/head
J. Nick Koston 2020-05-11 18:30:15 -05:00 committed by GitHub
parent dd22200a69
commit 3315c4c6c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 31 additions and 8 deletions

View File

@ -5,6 +5,8 @@ import socket
import voluptuous as vol import voluptuous as vol
from zeroconf import ( from zeroconf import (
DNSPointer,
DNSRecord,
InterfaceChoice, InterfaceChoice,
NonUniqueNameException, NonUniqueNameException,
ServiceBrowser, ServiceBrowser,
@ -75,6 +77,27 @@ def _get_instance(hass, default_interface=False):
return zeroconf return zeroconf
class HaServiceBrowser(ServiceBrowser):
"""ServiceBrowser that only consumes DNSPointer records."""
def update_record(self, zc: "Zeroconf", now: float, record: DNSRecord) -> None:
"""Pre-Filter update_record to DNSPointers for the configured type."""
#
# Each ServerBrowser currently runs in its own thread which
# processes every A or AAAA record update per instance.
#
# As the list of zeroconf names we watch for grows, each additional
# ServiceBrowser would process all the A and AAAA updates on the network.
#
# To avoid overwhemling the system we pre-filter here and only process
# DNSPointers for the configured record name (type)
#
if record.name != self.type or not isinstance(record, DNSPointer):
return
super().update_record(zc, now, record)
class HaZeroconf(Zeroconf): class HaZeroconf(Zeroconf):
"""Zeroconf that cannot be closed.""" """Zeroconf that cannot be closed."""
@ -166,10 +189,10 @@ def setup(hass, config):
) )
for service in ZEROCONF: for service in ZEROCONF:
ServiceBrowser(zeroconf, service, handlers=[service_update]) HaServiceBrowser(zeroconf, service, handlers=[service_update])
if HOMEKIT_TYPE not in ZEROCONF: if HOMEKIT_TYPE not in ZEROCONF:
ServiceBrowser(zeroconf, HOMEKIT_TYPE, handlers=[service_update]) HaServiceBrowser(zeroconf, HOMEKIT_TYPE, handlers=[service_update])
return True return True

View File

@ -68,7 +68,7 @@ async def test_setup(hass, mock_zeroconf):
with patch.object( with patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "ServiceBrowser", side_effect=service_update_mock zeroconf, "HaServiceBrowser", side_effect=service_update_mock
) as mock_service_browser: ) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_service_info_mock mock_zeroconf.get_service_info.side_effect = get_service_info_mock
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -87,7 +87,7 @@ async def test_setup(hass, mock_zeroconf):
async def test_setup_with_default_interface(hass, mock_zeroconf): async def test_setup_with_default_interface(hass, mock_zeroconf):
"""Test default interface config.""" """Test default interface config."""
with patch.object(hass.config_entries.flow, "async_init"), patch.object( with patch.object(hass.config_entries.flow, "async_init"), patch.object(
zeroconf, "ServiceBrowser", side_effect=service_update_mock zeroconf, "HaServiceBrowser", side_effect=service_update_mock
): ):
mock_zeroconf.get_service_info.side_effect = get_service_info_mock mock_zeroconf.get_service_info.side_effect = get_service_info_mock
assert await async_setup_component( assert await async_setup_component(
@ -100,7 +100,7 @@ async def test_setup_with_default_interface(hass, mock_zeroconf):
async def test_setup_without_default_interface(hass, mock_zeroconf): async def test_setup_without_default_interface(hass, mock_zeroconf):
"""Test without default interface config.""" """Test without default interface config."""
with patch.object(hass.config_entries.flow, "async_init"), patch.object( with patch.object(hass.config_entries.flow, "async_init"), patch.object(
zeroconf, "ServiceBrowser", side_effect=service_update_mock zeroconf, "HaServiceBrowser", side_effect=service_update_mock
): ):
mock_zeroconf.get_service_info.side_effect = get_service_info_mock mock_zeroconf.get_service_info.side_effect = get_service_info_mock
assert await async_setup_component( assert await async_setup_component(
@ -117,7 +117,7 @@ async def test_homekit_match_partial_space(hass, mock_zeroconf):
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "ServiceBrowser", side_effect=service_update_mock zeroconf, "HaServiceBrowser", side_effect=service_update_mock
) as mock_service_browser: ) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock("LIFX bulb") mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock("LIFX bulb")
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -134,7 +134,7 @@ async def test_homekit_match_partial_dash(hass, mock_zeroconf):
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "ServiceBrowser", side_effect=service_update_mock zeroconf, "HaServiceBrowser", side_effect=service_update_mock
) as mock_service_browser: ) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock( mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock(
"Rachio-fa46ba" "Rachio-fa46ba"
@ -153,7 +153,7 @@ async def test_homekit_match_full(hass, mock_zeroconf):
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "ServiceBrowser", side_effect=service_update_mock zeroconf, "HaServiceBrowser", side_effect=service_update_mock
) as mock_service_browser: ) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock("BSB002") mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock("BSB002")
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})