Avoid writing registries to disk during startup (#112662)

This commit is contained in:
J. Nick Koston 2024-03-07 19:14:42 -10:00 committed by GitHub
parent 0382d628a3
commit caefdc6192
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 100 additions and 42 deletions

View File

@ -14,6 +14,7 @@ from .normalized_name_base_registry import (
NormalizedNameBaseRegistryItems,
normalize_name,
)
from .registry import BaseRegistry
from .storage import Store
from .typing import UNDEFINED, UndefinedType
@ -22,7 +23,6 @@ EVENT_AREA_REGISTRY_UPDATED = "area_registry_updated"
STORAGE_KEY = "core.area_registry"
STORAGE_VERSION_MAJOR = 1
STORAGE_VERSION_MINOR = 6
SAVE_DELAY = 10
class EventAreaRegistryUpdatedData(TypedDict):
@ -86,7 +86,7 @@ class AreaRegistryStore(Store[dict[str, list[dict[str, Any]]]]):
return old_data
class AreaRegistry:
class AreaRegistry(BaseRegistry):
"""Class to hold a registry of areas."""
areas: NormalizedNameBaseRegistryItems[AreaEntry]
@ -273,11 +273,6 @@ class AreaRegistry:
self.areas = areas
self._area_data = areas.data
@callback
def async_schedule_save(self) -> None:
"""Schedule saving the area registry."""
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
@callback
def _data_to_save(self) -> dict[str, list[dict[str, Any]]]:
"""Return data of area registry to store in a file."""

View File

@ -31,6 +31,7 @@ from .deprecation import (
)
from .frame import report
from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes
from .registry import BaseRegistry
from .typing import UNDEFINED, UndefinedType
if TYPE_CHECKING:
@ -45,7 +46,7 @@ EVENT_DEVICE_REGISTRY_UPDATED = "device_registry_updated"
STORAGE_KEY = "core.device_registry"
STORAGE_VERSION_MAJOR = 1
STORAGE_VERSION_MINOR = 5
SAVE_DELAY = 10
CLEANUP_DELAY = 10
CONNECTION_BLUETOOTH = "bluetooth"
@ -456,7 +457,7 @@ class DeviceRegistryItems(UserDict[str, _EntryTypeT]):
return None
class DeviceRegistry:
class DeviceRegistry(BaseRegistry):
"""Class to hold a registry of devices."""
devices: DeviceRegistryItems[DeviceEntry]
@ -898,11 +899,6 @@ class DeviceRegistry:
self.deleted_devices = deleted_devices
self._device_data = devices.data
@callback
def async_schedule_save(self) -> None:
"""Schedule saving the device registry."""
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
@callback
def _data_to_save(self) -> dict[str, list[dict[str, Any]]]:
"""Return data of device registry to store in a file."""

View File

@ -52,6 +52,7 @@ from homeassistant.util.read_only_dict import ReadOnlyDict
from . import device_registry as dr, storage
from .device_registry import EVENT_DEVICE_REGISTRY_UPDATED
from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes
from .registry import BaseRegistry
from .typing import UNDEFINED, UndefinedType
if TYPE_CHECKING:
@ -61,7 +62,7 @@ T = TypeVar("T")
DATA_REGISTRY = "entity_registry"
EVENT_ENTITY_REGISTRY_UPDATED = "entity_registry_updated"
SAVE_DELAY = 10
_LOGGER = logging.getLogger(__name__)
STORAGE_VERSION_MAJOR = 1
@ -549,7 +550,7 @@ class EntityRegistryItems(UserDict[str, RegistryEntry]):
return [data[key] for key in self._area_id_index.get(area_id, ())]
class EntityRegistry:
class EntityRegistry(BaseRegistry):
"""Class to hold a registry of entities."""
deleted_entities: dict[tuple[str, str, str], DeletedRegistryEntry]
@ -1182,11 +1183,6 @@ class EntityRegistry:
self.entities = entities
self._entities_data = entities.data
@callback
def async_schedule_save(self) -> None:
"""Schedule saving the entity registry."""
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
@callback
def _data_to_save(self) -> dict[str, Any]:
"""Return data of entity registry to store in a file."""

View File

@ -14,6 +14,7 @@ from .normalized_name_base_registry import (
NormalizedNameBaseRegistryItems,
normalize_name,
)
from .registry import BaseRegistry
from .storage import Store
from .typing import UNDEFINED, EventType, UndefinedType
@ -21,7 +22,6 @@ DATA_REGISTRY = "floor_registry"
EVENT_FLOOR_REGISTRY_UPDATED = "floor_registry_updated"
STORAGE_KEY = "core.floor_registry"
STORAGE_VERSION_MAJOR = 1
SAVE_DELAY = 10
class EventFloorRegistryUpdatedData(TypedDict):
@ -44,7 +44,7 @@ class FloorEntry(NormalizedNameBaseRegistryEntry):
level: int = 0
class FloorRegistry:
class FloorRegistry(BaseRegistry):
"""Class to hold a registry of floors."""
floors: NormalizedNameBaseRegistryItems[FloorEntry]
@ -209,11 +209,6 @@ class FloorRegistry:
self.floors = floors
self._floor_data = floors.data
@callback
def async_schedule_save(self) -> None:
"""Schedule saving the floor registry."""
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
@callback
def _data_to_save(self) -> dict[str, list[dict[str, str | int | list[str] | None]]]:
"""Return data of floor registry to store in a file."""

View File

@ -14,6 +14,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.util.async_ import run_callback_threadsafe
import homeassistant.util.dt as dt_util
from .registry import BaseRegistry
from .storage import Store
DATA_REGISTRY = "issue_registry"
@ -21,7 +22,6 @@ EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED = "repairs_issue_registry_updated"
STORAGE_KEY = "repairs.issue_registry"
STORAGE_VERSION_MAJOR = 1
STORAGE_VERSION_MINOR = 2
SAVE_DELAY = 10
class IssueSeverity(StrEnum):
@ -92,7 +92,7 @@ class IssueRegistryStore(Store[dict[str, list[dict[str, Any]]]]):
return old_data
class IssueRegistry:
class IssueRegistry(BaseRegistry):
"""Class to hold a registry of issues."""
def __init__(self, hass: HomeAssistant, *, read_only: bool = False) -> None:
@ -259,11 +259,6 @@ class IssueRegistry:
self.issues = issues
@callback
def async_schedule_save(self) -> None:
"""Schedule saving the issue registry."""
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
@callback
def _data_to_save(self) -> dict[str, list[dict[str, str | None]]]:
"""Return data of issue registry to store in a file."""

View File

@ -14,6 +14,7 @@ from .normalized_name_base_registry import (
NormalizedNameBaseRegistryItems,
normalize_name,
)
from .registry import BaseRegistry
from .storage import Store
from .typing import UNDEFINED, EventType, UndefinedType
@ -21,7 +22,6 @@ DATA_REGISTRY = "label_registry"
EVENT_LABEL_REGISTRY_UPDATED = "label_registry_updated"
STORAGE_KEY = "core.label_registry"
STORAGE_VERSION_MAJOR = 1
SAVE_DELAY = 10
class EventLabelRegistryUpdatedData(TypedDict):
@ -44,7 +44,7 @@ class LabelEntry(NormalizedNameBaseRegistryEntry):
icon: str | None = None
class LabelRegistry:
class LabelRegistry(BaseRegistry):
"""Class to hold a registry of labels."""
labels: NormalizedNameBaseRegistryItems[LabelEntry]
@ -205,11 +205,6 @@ class LabelRegistry:
self.labels = labels
self._label_data = labels.data
@callback
def async_schedule_save(self) -> None:
"""Schedule saving the label registry."""
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
@callback
def _data_to_save(self) -> dict[str, list[dict[str, str | None]]]:
"""Return data of label registry to store in a file."""

View File

@ -0,0 +1,35 @@
"""Provide a base implementation for registries."""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any
from homeassistant.core import CoreState, HomeAssistant, callback
if TYPE_CHECKING:
from .storage import Store
SAVE_DELAY = 10
SAVE_DELAY_STARTING = 300
class BaseRegistry(ABC):
"""Class to implement a registry."""
hass: HomeAssistant
_store: Store
@callback
def async_schedule_save(self) -> None:
"""Schedule saving the registry."""
# Schedule the save past startup to avoid writing
# the file while the system is starting.
delay = (
SAVE_DELAY_STARTING if self.hass.state is CoreState.starting else SAVE_DELAY
)
self._store.async_delay_save(self._data_to_save, delay)
@callback
@abstractmethod
def _data_to_save(self) -> dict[str, Any]:
"""Return data of registry to store in a file."""

View File

@ -0,0 +1,51 @@
"""Tests for the registry."""
from typing import Any
from freezegun.api import FrozenDateTimeFactory
from homeassistant.core import CoreState, HomeAssistant
from homeassistant.helpers import storage
from homeassistant.helpers.registry import SAVE_DELAY, SAVE_DELAY_STARTING, BaseRegistry
from tests.common import async_fire_time_changed
class SampleRegistry(BaseRegistry):
"""Class to hold a registry of X."""
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the registry."""
self.hass = hass
self._store = storage.Store(hass, 1, "test")
self.save_calls = 0
def _data_to_save(self) -> None:
"""Return data of registry to save."""
self.save_calls += 1
return None
async def test_async_schedule_save(
hass: HomeAssistant, freezer: FrozenDateTimeFactory, hass_storage: dict[str, Any]
) -> None:
"""Test saving the registry."""
registry = SampleRegistry(hass)
hass.set_state(CoreState.starting)
registry.async_schedule_save()
freezer.tick(SAVE_DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert registry.save_calls == 0
freezer.tick(SAVE_DELAY_STARTING)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert registry.save_calls == 1
hass.set_state(CoreState.running)
registry.async_schedule_save()
freezer.tick(SAVE_DELAY)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert registry.save_calls == 2