diff --git a/homeassistant/helpers/issue_registry.py b/homeassistant/helpers/issue_registry.py index 11bde0edf6b..49dc2a36cb0 100644 --- a/homeassistant/helpers/issue_registry.py +++ b/homeassistant/helpers/issue_registry.py @@ -132,7 +132,7 @@ class IssueRegistry(BaseRegistry): translation_placeholders: dict[str, str] | None = None, ) -> IssueEntry: """Get issue. Create if it doesn't exist.""" - + self.hass.verify_event_loop_thread("async_get_or_create") if (issue := self.async_get_issue(domain, issue_id)) is None: issue = IssueEntry( active=True, @@ -152,7 +152,7 @@ class IssueRegistry(BaseRegistry): ) self.issues[(domain, issue_id)] = issue self.async_schedule_save() - self.hass.bus.async_fire( + self.hass.bus.async_fire_internal( EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED, {"action": "create", "domain": domain, "issue_id": issue_id}, ) @@ -174,7 +174,7 @@ class IssueRegistry(BaseRegistry): if replacement != issue: issue = self.issues[(domain, issue_id)] = replacement self.async_schedule_save() - self.hass.bus.async_fire( + self.hass.bus.async_fire_internal( EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED, {"action": "update", "domain": domain, "issue_id": issue_id}, ) @@ -184,11 +184,12 @@ class IssueRegistry(BaseRegistry): @callback def async_delete(self, domain: str, issue_id: str) -> None: """Delete issue.""" + self.hass.verify_event_loop_thread("async_delete") if self.issues.pop((domain, issue_id), None) is None: return self.async_schedule_save() - self.hass.bus.async_fire( + self.hass.bus.async_fire_internal( EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED, {"action": "remove", "domain": domain, "issue_id": issue_id}, ) @@ -196,6 +197,7 @@ class IssueRegistry(BaseRegistry): @callback def async_ignore(self, domain: str, issue_id: str, ignore: bool) -> IssueEntry: """Ignore issue.""" + self.hass.verify_event_loop_thread("async_ignore") old = self.issues[(domain, issue_id)] dismissed_version = ha_version if ignore else None if old.dismissed_version == dismissed_version: @@ -207,7 +209,7 @@ class IssueRegistry(BaseRegistry): ) self.async_schedule_save() - self.hass.bus.async_fire( + self.hass.bus.async_fire_internal( EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED, {"action": "update", "domain": domain, "issue_id": issue_id}, ) diff --git a/tests/helpers/test_issue_registry.py b/tests/helpers/test_issue_registry.py index 66fc9662f75..eb6a32540e9 100644 --- a/tests/helpers/test_issue_registry.py +++ b/tests/helpers/test_issue_registry.py @@ -1,5 +1,6 @@ """Test the repairs websocket API.""" +from functools import partial from typing import Any import pytest @@ -358,3 +359,71 @@ async def test_migration_1_1(hass: HomeAssistant, hass_storage: dict[str, Any]) registry: ir.IssueRegistry = hass.data[ir.DATA_REGISTRY] assert len(registry.issues) == 2 + + +async def test_get_or_create_thread_safety( + hass: HomeAssistant, issue_registry: ir.IssueRegistry +) -> None: + """Test call async_get_or_create_from a thread.""" + with pytest.raises( + RuntimeError, + match="Detected code that calls async_get_or_create from a thread. Please report this issue.", + ): + await hass.async_add_executor_job( + partial( + ir.async_create_issue, + hass, + "any", + "any", + is_fixable=True, + severity="error", + translation_key="any", + ) + ) + + +async def test_async_delete_issue_thread_safety( + hass: HomeAssistant, issue_registry: ir.IssueRegistry +) -> None: + """Test call async_delete_issue from a thread.""" + ir.async_create_issue( + hass, + "any", + "any", + is_fixable=True, + severity="error", + translation_key="any", + ) + + with pytest.raises( + RuntimeError, + match="Detected code that calls async_delete from a thread. Please report this issue.", + ): + await hass.async_add_executor_job( + ir.async_delete_issue, + hass, + "any", + "any", + ) + + +async def test_async_ignore_issue_thread_safety( + hass: HomeAssistant, issue_registry: ir.IssueRegistry +) -> None: + """Test call async_ignore_issue from a thread.""" + ir.async_create_issue( + hass, + "any", + "any", + is_fixable=True, + severity="error", + translation_key="any", + ) + + with pytest.raises( + RuntimeError, + match="Detected code that calls async_ignore from a thread. Please report this issue.", + ): + await hass.async_add_executor_job( + ir.async_ignore_issue, hass, "any", "any", True + )