Fix race in notify setup (#76954)
This commit is contained in:
parent
3eaa1c30af
commit
03fac0c529
3 changed files with 119 additions and 13 deletions
|
@ -1,6 +1,8 @@
|
|||
"""Provides functionality to notify people."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.persistent_notification as pn
|
||||
|
@ -40,13 +42,19 @@ PLATFORM_SCHEMA = vol.Schema(
|
|||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the notify services."""
|
||||
|
||||
platform_setups = async_setup_legacy(hass, config)
|
||||
|
||||
# We need to add the component here break the deadlock
|
||||
# when setting up integrations from config entries as
|
||||
# they would otherwise wait for notify to be
|
||||
# setup and thus the config entries would not be able to
|
||||
# setup their platforms.
|
||||
# setup their platforms, but we need to do it after
|
||||
# the dispatcher is connected so we don't miss integrations
|
||||
# that are registered before the dispatcher is connected
|
||||
hass.config.components.add(DOMAIN)
|
||||
await async_setup_legacy(hass, config)
|
||||
|
||||
if platform_setups:
|
||||
await asyncio.wait([asyncio.create_task(setup) for setup in platform_setups])
|
||||
|
||||
async def persistent_notification(service: ServiceCall) -> None:
|
||||
"""Send notification via the built-in persistsent_notify integration."""
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Coroutine
|
||||
from functools import partial
|
||||
from typing import Any, cast
|
||||
|
||||
|
@ -32,7 +33,10 @@ NOTIFY_SERVICES = "notify_services"
|
|||
NOTIFY_DISCOVERY_DISPATCHER = "notify_discovery_dispatcher"
|
||||
|
||||
|
||||
async def async_setup_legacy(hass: HomeAssistant, config: ConfigType) -> None:
|
||||
@callback
|
||||
def async_setup_legacy(
|
||||
hass: HomeAssistant, config: ConfigType
|
||||
) -> list[Coroutine[Any, Any, None]]:
|
||||
"""Set up legacy notify services."""
|
||||
hass.data.setdefault(NOTIFY_SERVICES, {})
|
||||
hass.data.setdefault(NOTIFY_DISCOVERY_DISPATCHER, None)
|
||||
|
@ -101,15 +105,6 @@ async def async_setup_legacy(hass: HomeAssistant, config: ConfigType) -> None:
|
|||
)
|
||||
hass.config.components.add(f"{DOMAIN}.{integration_name}")
|
||||
|
||||
setup_tasks = [
|
||||
asyncio.create_task(async_setup_platform(integration_name, p_config))
|
||||
for integration_name, p_config in config_per_platform(config, DOMAIN)
|
||||
if integration_name is not None
|
||||
]
|
||||
|
||||
if setup_tasks:
|
||||
await asyncio.wait(setup_tasks)
|
||||
|
||||
async def async_platform_discovered(
|
||||
platform: str, info: DiscoveryInfoType | None
|
||||
) -> None:
|
||||
|
@ -120,6 +115,12 @@ async def async_setup_legacy(hass: HomeAssistant, config: ConfigType) -> None:
|
|||
hass, DOMAIN, async_platform_discovered
|
||||
)
|
||||
|
||||
return [
|
||||
async_setup_platform(integration_name, p_config)
|
||||
for integration_name, p_config in config_per_platform(config, DOMAIN)
|
||||
if integration_name is not None
|
||||
]
|
||||
|
||||
|
||||
@callback
|
||||
def check_templates_warn(hass: HomeAssistant, tpl: template.Template) -> None:
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
"""The tests for notify services that change targets."""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import yaml
|
||||
|
||||
from homeassistant import config as hass_config
|
||||
from homeassistant.components import notify
|
||||
from homeassistant.const import SERVICE_RELOAD
|
||||
from homeassistant.const import SERVICE_RELOAD, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.reload import async_setup_reload_service
|
||||
|
@ -330,3 +331,99 @@ async def test_setup_platform_and_reload(hass, caplog, tmp_path):
|
|||
# Check if the dynamically notify services from setup were removed
|
||||
assert not hass.services.has_service(notify.DOMAIN, "testnotify2_c")
|
||||
assert not hass.services.has_service(notify.DOMAIN, "testnotify2_d")
|
||||
|
||||
|
||||
async def test_setup_platform_before_notify_setup(hass, caplog, tmp_path):
|
||||
"""Test trying to setup a platform before notify is setup."""
|
||||
get_service_called = Mock()
|
||||
|
||||
async def async_get_service(hass, config, discovery_info=None):
|
||||
"""Get notify service for mocked platform."""
|
||||
get_service_called(config, discovery_info)
|
||||
targetlist = {"a": 1, "b": 2}
|
||||
return NotificationService(hass, targetlist, "testnotify")
|
||||
|
||||
async def async_get_service2(hass, config, discovery_info=None):
|
||||
"""Get notify service for mocked platform."""
|
||||
get_service_called(config, discovery_info)
|
||||
targetlist = {"c": 3, "d": 4}
|
||||
return NotificationService(hass, targetlist, "testnotify2")
|
||||
|
||||
# Mock first platform
|
||||
mock_notify_platform(
|
||||
hass, tmp_path, "testnotify", async_get_service=async_get_service
|
||||
)
|
||||
|
||||
# Initialize a second platform testnotify2
|
||||
mock_notify_platform(
|
||||
hass, tmp_path, "testnotify2", async_get_service=async_get_service2
|
||||
)
|
||||
|
||||
hass_config = {"notify": [{"platform": "testnotify"}]}
|
||||
|
||||
# Setup the second testnotify2 platform from discovery
|
||||
load_coro = async_load_platform(
|
||||
hass, Platform.NOTIFY, "testnotify2", {}, hass_config=hass_config
|
||||
)
|
||||
|
||||
# Setup the testnotify platform
|
||||
setup_coro = async_setup_component(hass, "notify", hass_config)
|
||||
|
||||
load_task = asyncio.create_task(load_coro)
|
||||
setup_task = asyncio.create_task(setup_coro)
|
||||
|
||||
await asyncio.gather(load_task, setup_task)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert hass.services.has_service(notify.DOMAIN, "testnotify_a")
|
||||
assert hass.services.has_service(notify.DOMAIN, "testnotify_b")
|
||||
assert hass.services.has_service(notify.DOMAIN, "testnotify2_c")
|
||||
assert hass.services.has_service(notify.DOMAIN, "testnotify2_d")
|
||||
|
||||
|
||||
async def test_setup_platform_after_notify_setup(hass, caplog, tmp_path):
|
||||
"""Test trying to setup a platform after notify is setup."""
|
||||
get_service_called = Mock()
|
||||
|
||||
async def async_get_service(hass, config, discovery_info=None):
|
||||
"""Get notify service for mocked platform."""
|
||||
get_service_called(config, discovery_info)
|
||||
targetlist = {"a": 1, "b": 2}
|
||||
return NotificationService(hass, targetlist, "testnotify")
|
||||
|
||||
async def async_get_service2(hass, config, discovery_info=None):
|
||||
"""Get notify service for mocked platform."""
|
||||
get_service_called(config, discovery_info)
|
||||
targetlist = {"c": 3, "d": 4}
|
||||
return NotificationService(hass, targetlist, "testnotify2")
|
||||
|
||||
# Mock first platform
|
||||
mock_notify_platform(
|
||||
hass, tmp_path, "testnotify", async_get_service=async_get_service
|
||||
)
|
||||
|
||||
# Initialize a second platform testnotify2
|
||||
mock_notify_platform(
|
||||
hass, tmp_path, "testnotify2", async_get_service=async_get_service2
|
||||
)
|
||||
|
||||
hass_config = {"notify": [{"platform": "testnotify"}]}
|
||||
|
||||
# Setup the second testnotify2 platform from discovery
|
||||
load_coro = async_load_platform(
|
||||
hass, Platform.NOTIFY, "testnotify2", {}, hass_config=hass_config
|
||||
)
|
||||
|
||||
# Setup the testnotify platform
|
||||
setup_coro = async_setup_component(hass, "notify", hass_config)
|
||||
|
||||
setup_task = asyncio.create_task(setup_coro)
|
||||
load_task = asyncio.create_task(load_coro)
|
||||
|
||||
await asyncio.gather(load_task, setup_task)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert hass.services.has_service(notify.DOMAIN, "testnotify_a")
|
||||
assert hass.services.has_service(notify.DOMAIN, "testnotify_b")
|
||||
assert hass.services.has_service(notify.DOMAIN, "testnotify2_c")
|
||||
assert hass.services.has_service(notify.DOMAIN, "testnotify2_d")
|
||||
|
|
Loading…
Add table
Reference in a new issue