mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 04:37:06 +00:00
Move thread safety check in area_registry sooner (#116265)
It turns out we have custom components that are writing to the area registry using the async APIs from threads. We now catch it at the point async_fire is called. Instead we should check sooner and use async_fire_internal so we catch the unsafe operation before it can corrupt the registry.
This commit is contained in:
parent
c1572d9600
commit
b403c9f92e
@ -202,6 +202,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
|||||||
picture: str | None = None,
|
picture: str | None = None,
|
||||||
) -> AreaEntry:
|
) -> AreaEntry:
|
||||||
"""Create a new area."""
|
"""Create a new area."""
|
||||||
|
self.hass.verify_event_loop_thread("async_create")
|
||||||
normalized_name = normalize_name(name)
|
normalized_name = normalize_name(name)
|
||||||
|
|
||||||
if self.async_get_area_by_name(name):
|
if self.async_get_area_by_name(name):
|
||||||
@ -221,7 +222,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
|||||||
assert area.id is not None
|
assert area.id is not None
|
||||||
self.areas[area.id] = area
|
self.areas[area.id] = area
|
||||||
self.async_schedule_save()
|
self.async_schedule_save()
|
||||||
self.hass.bus.async_fire(
|
self.hass.bus.async_fire_internal(
|
||||||
EVENT_AREA_REGISTRY_UPDATED,
|
EVENT_AREA_REGISTRY_UPDATED,
|
||||||
EventAreaRegistryUpdatedData(action="create", area_id=area.id),
|
EventAreaRegistryUpdatedData(action="create", area_id=area.id),
|
||||||
)
|
)
|
||||||
@ -230,6 +231,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
|||||||
@callback
|
@callback
|
||||||
def async_delete(self, area_id: str) -> None:
|
def async_delete(self, area_id: str) -> None:
|
||||||
"""Delete area."""
|
"""Delete area."""
|
||||||
|
self.hass.verify_event_loop_thread("async_delete")
|
||||||
device_registry = dr.async_get(self.hass)
|
device_registry = dr.async_get(self.hass)
|
||||||
entity_registry = er.async_get(self.hass)
|
entity_registry = er.async_get(self.hass)
|
||||||
device_registry.async_clear_area_id(area_id)
|
device_registry.async_clear_area_id(area_id)
|
||||||
@ -237,7 +239,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
|||||||
|
|
||||||
del self.areas[area_id]
|
del self.areas[area_id]
|
||||||
|
|
||||||
self.hass.bus.async_fire(
|
self.hass.bus.async_fire_internal(
|
||||||
EVENT_AREA_REGISTRY_UPDATED,
|
EVENT_AREA_REGISTRY_UPDATED,
|
||||||
EventAreaRegistryUpdatedData(action="remove", area_id=area_id),
|
EventAreaRegistryUpdatedData(action="remove", area_id=area_id),
|
||||||
)
|
)
|
||||||
@ -266,6 +268,10 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
|||||||
name=name,
|
name=name,
|
||||||
picture=picture,
|
picture=picture,
|
||||||
)
|
)
|
||||||
|
# Since updated may be the old or the new and we always fire
|
||||||
|
# an event even if nothing has changed we cannot use async_fire_internal
|
||||||
|
# here because we do not know if the thread safety check already
|
||||||
|
# happened or not in _async_update.
|
||||||
self.hass.bus.async_fire(
|
self.hass.bus.async_fire(
|
||||||
EVENT_AREA_REGISTRY_UPDATED,
|
EVENT_AREA_REGISTRY_UPDATED,
|
||||||
EventAreaRegistryUpdatedData(action="update", area_id=area_id),
|
EventAreaRegistryUpdatedData(action="update", area_id=area_id),
|
||||||
@ -306,6 +312,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
|||||||
if not new_values:
|
if not new_values:
|
||||||
return old
|
return old
|
||||||
|
|
||||||
|
self.hass.verify_event_loop_thread("_async_update")
|
||||||
new = self.areas[area_id] = dataclasses.replace(old, **new_values) # type: ignore[arg-type]
|
new = self.areas[area_id] = dataclasses.replace(old, **new_values) # type: ignore[arg-type]
|
||||||
|
|
||||||
self.async_schedule_save()
|
self.async_schedule_save()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Tests for the Area Registry."""
|
"""Tests for the Area Registry."""
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -491,3 +492,40 @@ async def test_entries_for_label(
|
|||||||
|
|
||||||
assert not ar.async_entries_for_label(area_registry, "unknown")
|
assert not ar.async_entries_for_label(area_registry, "unknown")
|
||||||
assert not ar.async_entries_for_label(area_registry, "")
|
assert not ar.async_entries_for_label(area_registry, "")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_get_or_create_thread_checks(
|
||||||
|
hass: HomeAssistant, area_registry: ar.AreaRegistry
|
||||||
|
) -> None:
|
||||||
|
"""We raise when trying to create in the wrong thread."""
|
||||||
|
with pytest.raises(
|
||||||
|
RuntimeError,
|
||||||
|
match="Detected code that calls async_create from a thread. Please report this issue.",
|
||||||
|
):
|
||||||
|
await hass.async_add_executor_job(area_registry.async_create, "Mock1")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_update_thread_checks(
|
||||||
|
hass: HomeAssistant, area_registry: ar.AreaRegistry
|
||||||
|
) -> None:
|
||||||
|
"""We raise when trying to update in the wrong thread."""
|
||||||
|
area = area_registry.async_create("Mock1")
|
||||||
|
with pytest.raises(
|
||||||
|
RuntimeError,
|
||||||
|
match="Detected code that calls _async_update from a thread. Please report this issue.",
|
||||||
|
):
|
||||||
|
await hass.async_add_executor_job(
|
||||||
|
partial(area_registry.async_update, area.id, name="Mock2")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_delete_thread_checks(
|
||||||
|
hass: HomeAssistant, area_registry: ar.AreaRegistry
|
||||||
|
) -> None:
|
||||||
|
"""We raise when trying to delete in the wrong thread."""
|
||||||
|
area = area_registry.async_create("Mock1")
|
||||||
|
with pytest.raises(
|
||||||
|
RuntimeError,
|
||||||
|
match="Detected code that calls async_delete from a thread. Please report this issue.",
|
||||||
|
):
|
||||||
|
await hass.async_add_executor_job(area_registry.async_delete, area.id)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user